冷启动与热启动不是同一条回调路径
工程入口声明了 launchType: "singleton"。在这种单实例启动模式下,如果入口 UIAbility 已经存在,新的启动请求会进入 onNewWant;冷启动则先经过 onCreate,页面和 NavPathStack 随后才初始化。把路由代码只放在其中一个回调里,另一条路径就会丢失目标页面或直接拿不到已挂载的页面栈。
我在源码里看到的流程是:onCreate 暂存 Want 中的目标 ID,首页 aboutToAppear 附加 AppRouter 后读取并清空暂存值;onNewWant 则尝试直接进入目标页。这个骨架抓住了关键时序,但热启动时还应确认路由栈确实已准备好,并对外部参数做白名单校验。
先把入口过滤范围收窄
module.json5 中的 skills 决定哪些动作、实体和 URI 可以匹配 Ability。下面是匿名化的 HTTPS 过滤示例:
{
"skills": [
{
"actions": ["ohos.want.action.viewData"],
"entities": ["entity.system.browsable"],
"uris": [
{
"scheme": "https",
"host": "service.example.com",
"pathStartWith": "service/detail"
}
],
"domainVerify": true
}
]
}
pathStartWith 用于限制 URL 路径前缀;URI 筛选只决定候选 Ability 是否匹配,并不等于服务端数据授权。若按 App Linking 使用 HTTPS 链接,官方要求目标 Ability 的 actions、entities、https URI 与域名验证配置符合规则,还需完成相应的平台域名关联流程。当前工程快照的 URI 声明把 domainVerify 设为 false,所以不能将它描述成已经验证的 App Linking。
两条启动路径汇入一个“待处理目标”
冷启动收到目标时页面栈尚不存在,先将经过校验的目标写入暂存槽;热启动收到目标时,如果路由器已经附加,就直接导航,否则也走暂存。页面初始化只消费一次,并在导航前清空,避免页面重建时重复打开。
以下片段展示入口侧的分流。示例只接受简单的资源键;生产代码还应限制长度、字符集和允许访问的目标范围。
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import Url from '@ohos.url';
import { AppRouter } from '../router/AppRouter';
const PENDING_RESOURCE_KEY: string = 'pendingResourceId';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
AppStorage.setOrCreate<string>(PENDING_RESOURCE_KEY, this.readResourceId(want));
}
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
const resourceId: string = this.readResourceId(want);
if (resourceId.length === 0) {
return;
}
if (AppRouter.getStack() === undefined) {
AppStorage.setOrCreate<string>(PENDING_RESOURCE_KEY, resourceId);
return;
}
AppRouter.openResource(resourceId);
}
private readResourceId(want: Want): string {
const value: Object | undefined = want.parameters?.['resourceId'];
let resourceId: string = typeof value === 'string' ? value : '';
if (resourceId.length === 0 && want.uri !== undefined && want.uri.length > 0) {
try {
const parsedUrl = Url.URL.parseURL(want.uri);
resourceId = parsedUrl.params.get('resourceId') ?? '';
} catch (error) {
return '';
}
}
if (resourceId.length === 0 || resourceId.length > 64 || !/^[A-Za-z0-9-]+$/.test(resourceId)) {
return '';
}
return resourceId;
}
}
这里把同一个参数读取函数用于 onCreate 和 onNewWant:Want 的结构化参数优先,URI query 由 HarmonyOS URL API 解析。两条入口最终都经过长度和字符集校验,避免冷、热启动对参数规则理解不一致。不能把 URL 原文直接拼成页面路由,也不能仅靠 skills 匹配就信任传入值。
等导航容器就绪后再消费
首页完成路由器绑定后读取暂存参数,先清空,再压入目标页:
aboutToAppear(): void {
AppRouter.attach(this.pageStack);
const resourceId: string = AppStorage.get<string>(PENDING_RESOURCE_KEY) ?? '';
if (resourceId.length === 0) {
return;
}
AppStorage.setOrCreate<string>(PENDING_RESOURCE_KEY, '');
AppRouter.openResource(resourceId);
}
AppRouter.openResource 应是项目内唯一的“外部目标到页面栈”入口:检查目标类型、构造路由参数,并决定是复用当前详情页、替换栈顶还是新建页面。若每次都无条件 push,用户连续点开相同链接时会堆出一串重复页面;如果产品预期返回首页,则应在这个封装里先清理或替换页面栈。
路由之外的两道边界
- 输入校验:Want 参数是外部输入。ID 格式合法不代表用户有权访问对应数据,详情页仍要完成业务权限和数据状态校验。
- 域名校验:HTTPS URI 的 Ability 匹配和域名关联应在目标版本与平台控制台里实际验证。工程里存在 URI 声明并不代表外链已完成验证。
- 生命周期职责:
onCreate只做轻量解析和暂存,不在窗口创建前强行操作页面路由;耗时初始化放到合适的异步或页面阶段。 - 重复事件:根据页面栈现状处理重复 Want。必要时记录最近一次已消费的目标,或让 router 根据当前 destination 做复用。
参考资料
- UIAbilityContext 与 App Linking 约束
- UIAbility 生命周期:singleton 再次启动时进入 onNewWant
- module.json5 URI 筛选规则
- ArkTS URL 解析与查询参数 API
- Want 参数接收示例
上一篇:HarmonyOS 元服务卡片:从空生命周期到可更新的数据链路 · 回看:元服务配置核查。
