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



一、引言
DeepLink(深层链接) 允许用户通过 URL 直接跳转到应用内的特定页面。例如,点击一条商品推广链接,不是打开网页,而是直接唤起 App 并跳转到商品详情页——这就是 DeepLink 的核心场景。
在鸿蒙生态中,DeepLink 是实现应用间互通与 Web-to-App 引流的关键技术。本文将基于一个完整的 API 24 开源项目,详解 DeepLink 从配置、解析到路由分发的完整实现。
二、项目架构
本项目名为 DeepLinkDemo,模拟电商与用户系统混合场景,定义自定义 URL Scheme deeplinkdemo://:
| 目标页面 | 路由 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 {
const uri = want.uri;
// 步骤 1:提取 scheme、host、path
const schemeEndIndex = uri.indexOf('://');
const afterScheme = uri.substring(schemeEndIndex + 3);
const pathStartIndex = afterScheme.indexOf('/');
const pathname = afterScheme.substring(pathStartIndex);
// 步骤 2:去除 ?query 和 #fragment
const cleanPath = pathname.split('?')[0].split('#')[0];
// 步骤 3:提取路由 key 与参数值
const pathParts = cleanPath.split('/').filter(p => p.length > 0);
const routeKey = '/' + pathParts[0]; // "/product"
const paramValue = pathParts.slice(1).join('/'); // "1001"
// 步骤 4:路由表匹配
const targetPage = DEEPLINK_ROUTES[routeKey];
// 步骤 5:构造参数
const routeParams: Record<string, Object> = {};
routeParams['source'] = 'deeplink';
if (targetPage === 'pages/ProductDetail') routeParams['productId'] = paramValue;
else if (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 路由表设计:可扩展的中心化映射
const DEEPLINK_ROUTES: Record<string, string> = {
'/product': 'pages/ProductDetail',
'/profile': 'pages/ProfilePage',
};
const NOT_FOUND_PAGE = 'pages/NotFoundPage';
设计考量: 路由表使用 Record<string, string> 实现,Key 为路径前缀,Value 为页面的模块路径。新增页面时仅需在此添加一条记录,无需改动路由分发逻辑。参数通过 Record<string, Object> 透传,由目标页面自行解构。
3.5 目标页面:参数接收范式
目标页面通过 router.getParams() 获取 DeepLink 参数。以 ProductDetail.ets 为例:
aboutToAppear(): void {
const params = router.getParams() as Record<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()
// ❌ 编译错误
const url = new URL(uri);
// ✅ 手动字符串解析
const schemeEndIndex = uri.indexOf('://');
建议将 URI 解析封装为独立工具函数,便于复用。
4.2 索引签名类型无法直接赋值
// ❌ 编译错误:inline object literal 不可赋值给 Record<string, Object>
const params: Record<string, Object> = { source: 'deeplink' };
// ✅ 先创建空对象,再逐个赋值
const params: Record<string, Object> = {};
params['source'] = 'deeplink';
4.3 不支持 as const
// ❌ 不支持
const ROUTES = { '/product': 'pages/ProductDetail' } as const;
// ✅ 使用独立字符串常量
const ROUTE_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 回调中触发跳转:
private pendingDeepLink: 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 -a EntryAbility -b com.xiaoyouxi.myapplication \
-D deeplinkdemo://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 开发的你提供有价值的参考。
更多推荐




所有评论(0)