鸿蒙 DeepLink 深层链接实战:从零实现外部 URL 路由分发

📅 2026/7/4 14:44:38
鸿蒙 DeepLink 深层链接实战:从零实现外部 URL 路由分发
鸿蒙 DeepLink 深层链接实战从零实现外部 URL 路由分发一、引言DeepLink深层链接允许用户通过 URL 直接跳转到应用内的特定页面。例如点击一条商品推广链接不是打开网页而是直接唤起 App 并跳转到商品详情页——这就是 DeepLink 的核心场景。在鸿蒙生态中DeepLink 是实现应用间互通与 Web-to-App 引流的关键技术。本文将基于一个完整的 API 24 开源项目详解 DeepLink 从配置、解析到路由分发的完整实现。二、项目架构本项目名为 DeepLinkDemo模拟电商与用户系统混合场景定义自定义 URL Schemedeeplinkdemo://目标页面路由 KeyURL 示例商品详情页/productdeeplinkdemo://product/1001用户主页/profiledeeplinkdemo://profile/user_zhangsan404 兜底页—未匹配的任意路径页面结构由main_pages.json注册包含四个页面Index首页演示台、ProductDetail商品详情、ProfilePage用户主页、NotFoundPage404 兜底。路由流程图外部 DeepLink URL ↓ 系统匹配 uris → 分发至 EntryAbility ↓ onCreate / onNewWant → handleDeepLink() ↓ 路由表匹配 → 匹配成功 → router.pushUrl(目标页) ↓ 目标页接收 params 匹配失败 → NotFoundPage显示原始 URL三、核心实现详解3.1 模块声明module.json5DeepLink 的第一关是系统级配置——在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 不可用必须手动解析 URIhandleDeepLink(want:Want):void{consturiwant.uri;// 步骤 1提取 scheme、host、pathconstschemeEndIndexuri.indexOf(://);constafterSchemeuri.substring(schemeEndIndex3);constpathStartIndexafterScheme.indexOf(/);constpathnameafterScheme.substring(pathStartIndex);// 步骤 2去除 ?query 和 #fragmentconstcleanPathpathname.split(?)[0].split(#)[0];// 步骤 3提取路由 key 与参数值constpathPartscleanPath.split(/).filter(pp.length0);constrouteKey/pathParts[0];// /productconstparamValuepathParts.slice(1).join(/);// 1001// 步骤 4路由表匹配consttargetPageDEEPLINK_ROUTES[routeKey];// 步骤 5构造参数constrouteParams:Recordstring,Object{};routeParams[source]deeplink;if(targetPagepages/ProductDetail)routeParams[productId]paramValue;elseif(targetPagepages/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:Recordstring,string{/product:pages/ProductDetail,/profile:pages/ProfilePage,};constNOT_FOUND_PAGEpages/NotFoundPage;设计考量路由表使用Recordstring, string实现Key 为路径前缀Value 为页面的模块路径。新增页面时仅需在此添加一条记录无需改动路由分发逻辑。参数通过Recordstring, Object透传由目标页面自行解构。3.5 目标页面参数接收范式目标页面通过router.getParams()获取 DeepLink 参数。以ProductDetail.ets为例aboutToAppear():void{constparamsrouter.getParams()asRecordstring,Object;if(params){this.productIdparams[productId]!undefined?String(params[productId]):未知ID;this.sourceparams[source]!undefined?String(params[source]):direct;}this.loadProductData(this.productId);}关键注意点使用as Recordstring, 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()// ❌ 编译错误consturlnewURL(uri);// ✅ 手动字符串解析constschemeEndIndexuri.indexOf(://);建议将 URI 解析封装为独立工具函数便于复用。4.2 索引签名类型无法直接赋值// ❌ 编译错误inline object literal 不可赋值给 Recordstring, Objectconstparams:Recordstring,Object{source:deeplink};// ✅ 先创建空对象再逐个赋值constparams:Recordstring,Object{};params[source]deeplink;4.3 不支持as const// ❌ 不支持constROUTES{/product:pages/ProductDetail}asconst;// ✅ 使用独立字符串常量constROUTE_PRODUCT_DETAIL:stringpages/ProductDetail;4.4 BuilderParam 尾部闭包后不能链式调用// ❌ 编译错误CardContainer({title:标题}){/* ... */}.margin({top:16});// ✅ 需要外层包裹容器Column(){CardContainer({title:标题}){/* ... */};}.margin({top:16});五、最佳实践5.1 延迟跳转的改良方案当前代码使用setTimeout(500ms)确保页面就绪更优的做法是在onWindowStageCreate的loadContent回调中触发跳转privatependingDeepLink:Want|nullnull;onCreate(want:Want):void{if(want.uri)this.pendingDeepLinkwant;}onWindowStageCreate(windowStage:window.WindowStage):void{windowStage.loadContent(pages/Index,(){if(this.pendingDeepLink){this.handleDeepLink(this.pendingDeepLink);this.pendingDeepLinknull;}});}5.2 参数校验DeepLink URL 由外部传入格式不可控需做充分校验✅ 检查want.uri非空✅ 处理 path 为空的边界情况✅ 使用String()安全转换参数值✅ try-catch 包裹解析逻辑异常时降级到 4045.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 命令测试 DeepLinkhdc shell aa start-aEntryAbility-bcom.xiaoyouxi.myapplication\-Ddeeplinkdemo://product/1001用例输入 URL期望结果有效商品deeplinkdemo://product/1001商品页productId1001有效用户deeplinkdemo://profile/user_zhangsan用户页userIduser_zhangsan无效路径deeplinkdemo://unknown/path404 页面含查询参数deeplinkdemo://product/1001?refad商品页忽略查询参数六、总结本文基于鸿蒙 API 24 从零实现了 DeepLink 深层链接路由系统覆盖了从module.json5配置、EntryAbility双入口处理、URI 手动解析、路由表设计到目标页面参数接收的完整链路。核心要点配置先行skills.uris是 DeepLink 分发的先决条件三者缺一不可双入口处理onCreate应对冷启动onNewWant应对后台唤醒手动 URI 解析严格模式下需自行实现建议封装为工具函数可扩展路由表中心化配置新增页面零成本优雅降级404 兜底页确保任何未匹配链接都有去处防御性编程参数校验、异常捕获、类型安全转换缺一不可随着鸿蒙生态的发展DeepLink 的应用场景将不断扩展——从页面跳转到多应用协同、跨设备路由。ArkTS 严格模式虽带来一定约束但也为类型安全和代码健壮性提供了更强保障这正是生产级应用所需的品质。希望本文能为正在探索鸿蒙 DeepLink 开发的你提供有价值的参考。