【共创稿事节】HarmonyOS 7.0 Want隐式跳转:从接入到生产验证

Want 隐式跳转是鸿蒙开放体系里最容易被低估的机制------很多团队第一次接触它是在"应用拉起应用"的需求评审上,自己页面的跳转还停在 router.pushName。健康菜谱助手做过三个真场景:菜谱详情一键唤起系统分享、从冷启动 deeplink 直接进菜谱详情、接入了美食类意图的标准拉起。三个场景走完,才理解隐式跳转的核心不是"跳",是"声明与匹配":调用方描述意图,系统在所有 ability 的 skill 配置里找最合适的承接方,两边的契约写清楚,跳转才稳。这篇记录 Want 隐式跳转的匹配规则、配置写法、参数传递与生产验证。

一、显式与隐式:两种跳转的真实分野

显式跳转指名道姓------Want 里直接写 bundleName 和 abilityName,系统原样拉起。隐式跳转只说意图------action 是什么、要匹配哪些 entities、带什么 uri,系统按规则在全部已安装应用里筛出承接方。什么时候用哪种,我的判断标准:应用内部跳转用显式(路由表或 Want 均可,值在组件树里传更省事);跨应用跳转用隐式,因为目标应用的 bundleName 不可控,写死了就是脆弱契约。菜谱分享的场景里,承接方可能是系统分享服务、可能是第三方社交应用、可能是用户装的任意浏览器------这种"目标不定"的跳转只有隐式能做。

维度 显式跳转 隐式跳转
目标指定 bundleName+abilityName action/entities/uri
目标范围 唯一确定 系统匹配,可能多个
契约维护 写死,目标改名即断 按标准声明,目标可替换
典型场景 应用内页面跳转 分享/支付/查看/意图拉起
匹配失败 几乎不失败 无承接方抛异常

二、action 与 entities:匹配的两个轴

隐式匹配走两个轴:action 描述"做什么",entities 描述"目标的类别"。系统内置的 action 常量里最常用的是 ACTION_SEND_DATA(分享数据)、ACTION_VIEW_DATA(查看数据)、ACTION_SELECT(选择)。entities 里 ENTITY_CARD 是卡片、ENTITY_DATA 是数据类目标。匹配规则是"且"关系:Want 里声明的每个 action 和 entity 都必须被目标 skill 配置覆盖才算命中。一个实际教训:最初分享菜谱图片时 Want 里写了 ACTION_SEND_DATA 加两个 entities,结果系统分享面板只弹出一个应用------排查半天发现第二个 entity 太具体,把大部分应用的 skill 配置筛掉了。砍掉非必要 entity 后,面板恢复正常。结论:entities 按"必须满足的最小类别"声明,多一个都是筛选损失。

ts 复制代码
// 调用方:构造隐式 Want 唤起系统分享
import { common, wantConstant } from '@kit.AbilityKit'

const want: common.Want = {
  action: wantConstant.Action.ACTION_SEND_DATA,
  entities: [wantConstant.Entity.ENTITY_DATA],
  type: 'text/plain',
  parameters: {
    title: recipe.name,
    'ability.params.stream': 'internal://share/recipe-' + recipe.id
  }
}
context.startAbility(want).catch(err => showShareFailToast(err))

三、被拉起方:module.json5 里的 skill 声明

承接方的能力通过 module.json5 的 skills 声明,actions、entities、uris、type 四个字段对应匹配的四个维度。写 skill 的原则:每个 skill 只做一件事------一个 ability 可以有多个 skill,但每个 skill 的 action 加 entity 组合应该唯一,不要把不相关的匹配塞进同一个 skill。 uris 里的 scheme 是自有协议(如 recipeapp://),host 是资源域名,path 是路径模板。声明过宽(只写 action 不写 type)会把不该接的意图也接进来,用户在分享面板里看到你的应用选了却没反应;声明过窄则接不到。健康菜谱助手的承接 skill 只声明两个:查看菜谱(scheme 协议)和接收文本分享,每个都有明确的 type 或 uri 收口。

json5 复制代码
// module.json5:承接方 skill 声明
"abilities": [{
  "name": "RecipeEntryAbility",
  "skills": [{
    "actions": ["ohos.want.action.viewData"],
    "uris": [{ "scheme": "recipeapp", "host": "detail" }]
  }, {
    "actions": ["ohos.want.action.sendData"],
    "type": "text/plain"
  }]
}]

四、uri 匹配:scheme、host、path 的逐级规则

uri 匹配是逐级收紧的:scheme 匹配再看 host,host 匹配再看 path,path 支持通配(/.* 匹配任意子路径)。deeplink 场景"recipeapp://detail/123"要落到菜谱 123 的详情页,承接方 uris 声明 scheme=recipeapp、host=detail,path 不写(全收),启动后在 onNewWant 里解析 want.uri 提取 id。这里踩过两个坑:一是调用方 uri 里带 query(recipeapp://detail/123?from=share)而承接方声明了具体 path,通配没配好导致匹配失败,改成 path 不声明加手动解析才稳;二是 scheme 全小写的问题------有个版本的 uri 拼接里混进了大写,真机上匹配静默失败,日志里只有一行 want not found。scheme 与 host 一律小写、拼完统一 toLowerCase,是那之后的硬规矩。

ts 复制代码
// 承接方:解析 deeplink 参数
onNewWant(want: common.Want, launchParam: common.LaunchParam) {
  const uri = want.uri ?? ''
  if (uri.startsWith('recipeapp://detail/')) {
    const id = uri.split('/').pop()?.split('?')[0] ?? ''
    this.jumpToDetail(id, uri.includes('from=share') ? 'share' : 'direct')
  }
}

五、parameters 传参:序列化与体量的边界

Want 的 parameters 是一个键值字典,基础类型直接放,对象走 JSON 序列化。它的体量边界比组件参数宽得多,但也不是无底洞------超大数据(图片二进制、整本菜谱库)不适合塞进 parameters,正确做法是传引用(internal:// 协议的临时文件路径或 URI),让承接方自己读。分享场景的实际做法:菜谱全文 20KB 以内直接 JSON 塞 parameters 的 detail 字段;带图分享则把图先存到临时目录,parameters 里只传文件路径。另一个注意点:parameters 的键名避免与应用内组件参数混淆,统一加前缀(share.title、share.recipeId),防止承接方解析时和系统保留键撞名。

数据类型 传递方式 上限经验值
基础类型/短文本 parameters 直传 几十 KB 内
结构化对象 JSON.stringify 后直传 50KB 内
图片/文件 临时目录+路径引用 受沙箱限制
超大内容 只传 id,对端拉取 不限

六、真实排查:want not found 的三层定位

隐式跳转的失败形态只有一个:startAbility 抛 16000001(want not found),但原因有三层。第一层:目标应用未安装------跨应用分享菜单要按"已安装可承接"过滤,未安装就静默不出面板,这不算异常;第二层:skill 声明与 Want 不匹配------action、entity、type、uri 任一维度没对上;第三层:flags 问题------wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION 之类的权限 flag 在特定场景必须带,漏带时部分承接方拒绝响应。排查按"先自测契约、再系统验证、最后真机日志"三步:先用 abilityManager 查询匹配结果,再在 hdc shell 里用 aa start 手动发起同样意图,最后看 hilog 里 AbilityManagerService 的匹配日志。那次大写 scheme 的问题就是 aa start 直接把错误打在脸上才定位的。

ts 复制代码
// 自测:查询当前隐式 Want 能匹配到谁
import { abilityManager } from '@kit.AbilityKit'
const queryInfo = {
  action: 'ohos.want.action.sendData',
  entities: ['entity.system.home'],
  type: 'text/plain'
}
abilityManager.queryAbilities(queryInfo).then(list => {
  console.info(`match count: ${list.length}`)
})

七、onNewWant 与冷启动:两种进入路径都要接

承接 deeplink 的 ability 有两种进入状态:冷启动走 onCreate,热启动走 onNewWant------两条路径都要写解析逻辑,漏一条就是"有时候能进详情有时候进不了"。健康菜谱助手的做法是把 Want 解析收敛成一个纯函数 parseRecipeWant(want),onCreate 和 onNewWant 都调它,返回统一的路由指令对象,再由入口页执行跳转。这样两处逻辑不会漂移,单测也只需要覆盖这一个函数。另一个细节:onNewWant 触发时页面栈可能深在编辑页,直接跳详情要把栈管理想清楚------我们的策略是清到首页再 push 详情,保证返回链路符合直觉。

ts 复制代码
// 统一解析:onCreate 与 onNewWant 共用
function parseRecipeWant(want: common.Want): RouteCmd {
  const uri = want.uri ?? ''
  if (uri.startsWith('recipeapp://detail/')) {
    return { page: 'detail', id: uri.split('/').pop()?.split('?')[0] ?? '' }
  }
  if (want.parameters?.['share.recipeId']) {
    return { page: 'detail', id: String(want.parameters['share.recipeId']) }
  }
  return { page: 'home' }
}

八、安全边界:隐式跳转的两条防线

隐式跳转的开放性带来两个安全问题。防线一是输入校验:承接方收到的 want.uri 和 parameters 是外部输入,等同 Web 里的 query string------解析出的 id 必须校验格式(纯数字)再进数据库查询,防止注入式的构造串。防线二是权限最小化:给外部 URI 授权只开读(FLAG_AUTH_READ_URI_PERMISSION),分享出去的临时文件用完即删;承接敏感数据(用户菜谱草稿)的 skill 不对外开放,用显式跳转在应用内消化。另外分享内容的脱敏在构造 Want 之前做------parameters 里一旦塞进去,对接了无数未知承接方,再想收回来就不可能了。

九、生产验证:三场景回归清单

隐式跳转的回归测试固定三个场景:一、系统分享面板------从菜谱详情发起分享,验证面板出现、文本带出、选每个候选应用后对方正常收到;二、deeplink 冷启------杀进程后 adb 唤起 recipeapp://detail/123,验证落详情页、返回栈正确;三、deeplink 热启------应用在编辑页时唤起,验证清栈跳详情、编辑内容不丢(丢不丢是产品决策,但行为要确定)。三个场景各配一条 hilog 断言(parseRecipeWant 的解析结果),CI 跑不起真机就人工跑,但清单不能少------Want 相关代码一年改不了三次,可每次改完漏测都是用户先发现。

十、总结:契约思维的四个落点

收束成四点:一、跨应用跳转必隐式,契约写在 skill 里,声明按"最小必要"收口;二、uri 匹配逐级收紧,scheme 与 host 全小写,path 谨慎声明;三、parameters 传小不传大,大内容走引用,键名加前缀防撞;四、onCreate 与 onNewWant 双路径统一解析,生产回归三场景固定跑。隐式跳转的本质是解耦------调用方不认识承接方,双方只认系统撮合的标准契约。契约思维一旦建立,后面接意图框架、接服务卡片交互,走的都是同一条路。

参考资料:

  • 华为开发者:Want 隐式 Want 匹配规则
  • 华为开发者:module.json5 skills 标签说明
  • 华为开发者:startAbility 与 URI 权限

#鸿蒙 #HarmonyOS 7(API 26)

相关推荐
NJPJI_Yang43 分钟前
鸿蒙 HarmonyOS 7 应用内存优化:appMemoryOptimizer 释放 so 页缓存实战
pytorch·深度学习·harmonyos
余槐i2 小时前
Spring Boot 3.2 偶发500无日志:线程池复用导致请求上下文丢失
spring boot·redis·后端·mysql·性能优化
熊猫钓鱼>_>2 小时前
三方库 NDK 原生模块跨平台适配与调试【鸿蒙心迹】
开发语言·华为·ai编程·harmonyos·鸿蒙·openharmony·鸿蒙心迹
1314lay_10072 小时前
ABAP 程序性能优化总结
经验分享·笔记·性能优化
行者-全栈开发13 小时前
【鸿蒙心迹】三方库移植(二)——CMake 交叉编译与符号冲突排查实录(HarmonyOS 7.x)
harmonyos·cmake·交叉编译·三方库移植·ohos·符号冲突
梦想不只是梦与想15 小时前
鸿蒙三方库源码调试
harmonyos·断点调试·三方源码调试
用户1179104883317 小时前
AI输出满屏井号星号?hmharness把Markdown渲染成终端表格 GitHub:swsgbl/hmharness
ai编程·harmonyos
了一12319 小时前
鸿蒙ArkWeb 长截图(滚动截图)技术方案总结
harmonyos
数据库小学妹20 小时前
MySQL磁盘IO瓶颈:iostat与刷脏fsync
数据库·mysql·性能优化