鸿蒙 DeepLink 深层链接实战:从零实现外部 URL 路由分发
一、引言
DeepLink(深层链接)允许用户通过 URL 直接跳转到应用内的特定页面。例如,点击一条商品推广链接,不是打开网页,而是直接唤起 App 并跳转到商品详情页——这就是 DeepLink 的核心场景。
在鸿蒙生态中,DeepLink 是实现应用间互通与 Web-to-App 引流的关键技术。本文将基于一个完整的 API 24 开源项目,详解 DeepLink 从配置、解析到路由分发的完整实现。
二、项目架构
本项目名为 DeepLinkDemo,模拟电商与用户系统混合场景,定义自定义 URL Schemedeeplinkdemo://:
| 目标页面 | 路由 Key | URL 示例 |
|---|---|---|
| 商品详情页 | /product | deeplinkdemo://product/1001 |
| 用户主页 | /profile | deeplinkdemo://profile/user_zhangsan |
| 404 兜底页 | — | 未匹配的任意路径 |
页面结构由main_pages.json注册,包含四个页面:Index(首页演示台)、ProductDetail(商品详情)、ProfilePage(用户主页)、NotFoundPage(404 兜底)。
路由流程图:
外部 DeepLink URL ↓ 系统匹配 uris → 分发至 EntryAbility ↓ onCreate / onNewWant → handleDeepLink() ↓ 路由表匹配 → 匹配成功 → router.pushUrl(目标页) ↓ 目标页接收 params 匹配失败 → NotFoundPage(显示原始 URL)三、核心实现详解
3.1 模块声明(module.json5)
DeepLink 的第一关是系统级配置——在module.json5的skills中声明支持的 URL 模式:
{ "abilities": [{ "name": "EntryAbility", "skills": [{ "actions": ["ohos.want.action.viewData"], "entities": ["entity.system.browsable"], "uris": [ { "scheme": "deeplinkdemo", "host": "www.deeplinkdemo.com", "pathStartWith": "/product" }, { "scheme": "deeplinkdemo", "host": "www.deeplinkdemo.com", "pathStartWith": "/profile" }, { "scheme": "deeplinkdemo", "host": "www.deeplinkdemo.com", "pathStartWith": "/" } ] }] }] }要点:ohos.want.action.viewData表示 Ability 能展示数据;entity.system.browsable标识为可浏览目标,是 DeepLink 匹配的必要条件。第三条pathStartWith: "/"作为通配兜底,将所有未精确匹配的链接收归应用内部二次路由。API 24 要求scheme、host、pathStartWith三项齐全才能匹配。
3.2 双入口:onCreate 与 onNewWant
// 冷启动:应用首次启动或进程被杀死后重新拉起onCreate(want:Want,launchParam:AbilityConstant.LaunchParam):void{if(want.uri)this.handleDeepLink(want);}// 热启动:应用已在后台,被 DeepLink 重新唤醒onNewWant(want:Want,launchParam:AbilityConstant.LaunchParam):void{if(want.uri)this.handleDeepLink(want);}两者的核心区别在于窗口是否就绪:onCreate在执行时onWindowStageCreate尚未完成,首页(pages/Index)仍在加载,因此代码中使用了setTimeout(500ms)延迟跳转;onNewWant时窗口已存在,理论上可以立即跳转,但为了统一处理逻辑,同样走延迟路径。
优化方案:更稳健的做法是在
onCreate中暂存 want,待loadContent回调确认后再执行handleDeepLink,避免硬编码延迟。
3.3 手动 URI 解析引擎
在 ArkTS 严格模式下,new URL()API 不可用,必须手动解析 URI:
handleDeepLink(want:Want):void{consturi=want.uri;// 步骤 1:提取 scheme、host、pathconstschemeEndIndex=uri.indexOf('://');constafterScheme=uri.substring(schemeEndIndex+3);constpathStartIndex=afterScheme.indexOf('/');constpathname=afterScheme.substring(pathStartIndex);// 步骤 2:去除 ?query 和 #fragmentconstcleanPath=pathname.split('?')[0].split('#')[0];// 步骤 3:提取路由 key 与参数值constpathParts=cleanPath.split('/').filter(p=>p.length>0);constrouteKey='/'+pathParts[0];// "/product"constparamValue=pathParts.slice(1).join('/');// "1001"// 步骤 4:路由表匹配consttargetPage=DEEPLINK_ROUTES[routeKey];// 步骤 5:构造参数constrouteParams:Record<string,Object>={};routeParams['source']='deeplink';if(targetPage==='pages/ProductDetail')routeParams['productId']=paramValue;elseif(targetPage==='pages/ProfilePage')routeParams['userId']=paramValue;// 步骤 6:延迟执行跳转setTimeout(()=>{if(targetPage){router.pushUrl({url:targetPage,params:routeParams});}else{router.pushUrl({url:'pages/NotFoundPage',params:{originalUrl:uri,source:'deeplink'}});}},500);}该实现采用分层解析策略:先分离协议结构,再清理冗余信息,最后提取语义化的路由 Key 和参数。每一层职责单一,便于扩展和测试。
3.4 路由表设计:可扩展的中心化映射
constDEEPLINK_ROUTES:Record<string,string>={'/product':'pages/ProductDetail','/profile':'pages/ProfilePage',};constNOT_FOUND_PAGE='pages/NotFoundPage';设计考量:路由表使用Record<string, string>实现,Key 为路径前缀,Value 为页面的模块路径。新增页面时仅需在此添加一条记录,无需改动路由分发逻辑。参数通过Record<string, Object>透传,由目标页面自行解构。
3.5 目标页面:参数接收范式
目标页面通过router.getParams()获取 DeepLink 参数。以ProductDetail.ets为例:
aboutToAppear():void{constparams=router.getParams()asRecord<string,Object>;if(params){this.productId=params['productId']!==undefined?String(params['productId']):'未知ID';this.source=params['source']!==undefined?String(params['source']):'direct';}this.loadProductData(this.productId);}关键注意点:
- 使用
as Record<string, Object>类型断言(ArkTS 严格模式要求) - 通过
String()显式转换参数值,因为Object无法直接赋值给string类型 - 通过
source === 'deeplink'区分外部跳转与内导航,UI 展示不同提示 - 加载模拟数据时需处理 ID 不存在的默认情况
3.6 404 兜底策略
当 DeepLink 与路由表无匹配时,跳转至NotFoundPage。该页面展示未匹配的原始 URL,列出所有支持的链接格式,并提供「返回首页」按钮:
⚠️ 未找到页面 404 该路径未注册任何页面 原始 DeepLink URL: deeplinkdemo://unknown/path ✅ 支持的 DeepLink 格式: deeplinkdemo://product/{商品ID} deeplinkdemo://profile/{用户ID} [← 返回首页]设计原则:错误页面应当有信息、有引导、有出口。显示原始 URL 帮助排障;列出正确格式减少用户困惑;返回按钮避免用户陷入死胡同。
四、ArkTS 严格模式踩坑实录
API 24 强制使用 ArkTS 严格模式,以下是核心注意事项。
4.1 不支持new URL()
// ❌ 编译错误consturl=newURL(uri);// ✅ 手动字符串解析constschemeEndIndex=uri.indexOf('://');建议将 URI 解析封装为独立工具函数,便于复用。
4.2 索引签名类型无法直接赋值
// ❌ 编译错误:inline object literal 不可赋值给 Record<string, Object>constparams:Record<string,Object>={source:'deeplink'};// ✅ 先创建空对象,再逐个赋值constparams:Record<string,Object>={};params['source']='deeplink';4.3 不支持as const
// ❌ 不支持constROUTES={'/product':'pages/ProductDetail'}asconst;// ✅ 使用独立字符串常量constROUTE_PRODUCT_DETAIL:string='pages/ProductDetail';4.4 @BuilderParam 尾部闭包后不能链式调用
// ❌ 编译错误CardContainer({title:'标题'}){/* ... */}.margin({top:16});// ✅ 需要外层包裹容器Column(){CardContainer({title:'标题'}){/* ... */};}.margin({top:16});五、最佳实践
5.1 延迟跳转的改良方案
当前代码使用setTimeout(500ms)确保页面就绪,更优的做法是在onWindowStageCreate的loadContent回调中触发跳转:
privatependingDeepLink:Want|null=null;onCreate(want:Want):void{if(want.uri)this.pendingDeepLink=want;}onWindowStageCreate(windowStage:window.WindowStage):void{windowStage.loadContent('pages/Index',()=>{if(this.pendingDeepLink){this.handleDeepLink(this.pendingDeepLink);this.pendingDeepLink=null;}});}5.2 参数校验
DeepLink URL 由外部传入,格式不可控,需做充分校验:
- ✅ 检查
want.uri非空 - ✅ 处理 path 为空的边界情况
- ✅ 使用
String()安全转换参数值 - ✅ try-catch 包裹解析逻辑,异常时降级到 404
5.3 日志埋点
在关键路径使用hilog埋点,便于链路追踪:
hilog.info(TAG,'收到 DeepLink 请求: %s',uri);// 入口hilog.info(TAG,'路由匹配结果: %s',targetPage);// 匹配hilog.warn(TAG,'路由未匹配: %s',uri);// 失配hilog.error(TAG,'DeepLink 解析异常: %s',errMsg);// 异常5.4 测试验证
通过 hdc 命令测试 DeepLink:
hdc shell aa start-aEntryAbility-bcom.xiaoyouxi.myapplication\-Ddeeplinkdemo://product/1001| 用例 | 输入 URL | 期望结果 |
|---|---|---|
| 有效商品 | deeplinkdemo://product/1001 | 商品页,productId=1001 |
| 有效用户 | deeplinkdemo://profile/user_zhangsan | 用户页,userId=user_zhangsan |
| 无效路径 | deeplinkdemo://unknown/path | 404 页面 |
| 含查询参数 | deeplinkdemo://product/1001?ref=ad | 商品页,忽略查询参数 |
六、总结
本文基于鸿蒙 API 24 从零实现了 DeepLink 深层链接路由系统,覆盖了从module.json5配置、EntryAbility双入口处理、URI 手动解析、路由表设计到目标页面参数接收的完整链路。
核心要点:
- 配置先行:
skills.uris是 DeepLink 分发的先决条件,三者缺一不可 - 双入口处理:
onCreate应对冷启动,onNewWant应对后台唤醒 - 手动 URI 解析:严格模式下需自行实现,建议封装为工具函数
- 可扩展路由表:中心化配置,新增页面零成本
- 优雅降级:404 兜底页确保任何未匹配链接都有去处
- 防御性编程:参数校验、异常捕获、类型安全转换缺一不可
随着鸿蒙生态的发展,DeepLink 的应用场景将不断扩展——从页面跳转到多应用协同、跨设备路由。ArkTS 严格模式虽带来一定约束,但也为类型安全和代码健壮性提供了更强保障,这正是生产级应用所需的品质。希望本文能为正在探索鸿蒙 DeepLink 开发的你提供有价值的参考。