HarmonyOS API 26 Navigation 跨包路由:router_map、HSP 按需加载与失败回落
模块化应用不能把跨包跳转理解成"换一个字符串"。页面名称、模块边界、router_map、按需加载、错误恢复和路由栈所有权必须形成同一条可验证链路。

一、跨包路由解决什么问题
随着功能拆入 HAP、HSP 或 HAR,入口模块不应直接依赖每个页面组件。系统路由表把页面名称映射到来源文件和 Builder,调用方通过 NavPathStack 提交名称,由平台完成模块定位、组件构建和页面跳转。
解耦并不代表无需契约。名称冲突、模块未安装、下载失败、Builder 不匹配仍需要应用侧治理。
二、官方能力边界
华为跨包路由指南说明,Navigation 提供系统路由表和自定义路由表;从 API 12 起可用系统路由表实现动态路由,各业务模块可以独立配置 router_map.json。官方资料还指出,从 API 23 起支持跳转按需加载的 HSP 页面。
具体模块类型、打包约束、字段定义和设备支持以目标 API 26 文档及工程模板为准。
三、router_map的三个核心字段
常见映射包含页面唯一名称、页面源码路径和对外 Builder 名称。字段必须与文件、导出函数和模块资源一致。
json
{
"name": "orders/OrderDetail",
"pageSourceFile": "src/main/ets/pages/OrderDetail.ets",
"buildFunction": "OrderDetailBuilder"
}
这段是结构示意,不代表已在真实工程验证。
四、module.json5必须注册路由表
创建 profile 文件不等于平台会读取它,还要在对应模块配置中引用。路径、资源名和模块边界错误都会导致运行时找不到目标。
json5
{
module: {
routerMap: "$profile:router_map"
}
}
修改后需要真实 HarmonyOS 构建才能确认语法和资源解析。
五、页面名称采用命名空间
多个业务包都叫 Detail 时容易冲突。建议使用稳定命名空间,例如 orders/OrderDetail、account/Profile,并把名称集中到公共契约模块。名称是路由身份,不应随中文标题或运营文案变化。
六、契约模块只放类型和常量
公共模块可以暴露路由名、参数 DTO 与结果类型,但不要反向依赖具体页面实现。Product 依赖功能契约,Feature 实现 Builder,依赖方向保持清晰。
ts
export const OrderRoutes = { detail: 'orders/OrderDetail' } as const
七、参数必须跨包可理解
调用方和目标包可能独立演进。参数使用小型、显式、版本化 DTO,避免传页面实例、Context、函数或大对象。稳定业务 ID、来源枚举和 schemaVersion 通常足够。
八、Builder是页面构建边界
router_map 中的 buildFunction 必须对应可访问的实例化方法,且最终构建有效 NavDestination。名称正确但 Builder 未导出、签名不匹配或未产生目标节点,仍会跳转失败。
九、系统路由表不等于网络下载
已随包安装的模块与按需 HSP 是不同准备状态。普通跨包解析不必假设联网;按需加载则可能受网络、空间、版本和签名影响。文章与代码必须明确具体场景。
十、按需HSP先做模块准备
对需要下载的目标,模块准备成功和页面入栈是两个检查点。ModuleService 确认网络条件、请求模块并验证可用性;之后 RouteCoordinator 再调用 NavPathStack。不要在下载过程中连续 push 同一页面。

十一、重复请求按模块和页面合并
多个入口同时请求同一按需页面时,下载任务应按 moduleId 合并,路由任务再按 requestKey 合并。模块共享不代表页面实例共享,两层去重要分别设计。
ts
const moduleTask = modulePending.getOrCreate(moduleId, prepareModule)
await moduleTask
return routePending.getOrCreate(requestKey, pushTarget)
十二、离线场景要提前判断
如果模块尚未准备且设备离线,立即进入可解释回落,不要让用户长时间等待。若模块已经可用,离线不应阻断本地页面跳转。策略不能简单写成"无网禁止所有跨包路由"。
十三、失败回落必须保留上下文
目标不可用时默认保留来源页,并提供重试、返回主页或打开已安装替代页面。不要 clear 全栈,也不要跳转到另一个同样依赖缺失模块的错误页。
十四、区分模块错误与路由错误
模块下载失败、校验失败、Builder 未注册、NavDestination 不存在和参数错误是不同阶段。统一提示可以简洁,但诊断事件必须保留阶段和受控错误码,才能定位根因。
十五、路由表构建检查应前置
CI 或本地脚本可以检查名称唯一、文件路径存在、Builder 名称非空、契约常量覆盖和重复 namespace。静态检查不能替代 hvigor 构建,但能在运行前发现大量配置错误。
十六、跨包路由仍遵守栈所有权
页面来自另一个 HSP,不代表应该操作另一个 Navigation。请求必须交给当前容器拥有的 NavPathStack;父子栈场景由上层协调器选择正确 owner。禁止功能包获取全局控制器任意跳转。
十七、每个Navigation仍是一栈一对象
官方页面路由指南强调 NavPathStack 与 Navigation 一一对应、不可复用。模块化项目尤其容易把一个全局栈注入所有页面,最终导致工作区、弹窗流和详情流相互污染。
十八、动态入口需要权限复核
按需模块准备完成时,用户登录态和权限可能已经变化。入栈前重新检查 owner generation、账号代次和业务授权。模块下载成功只是资源可用,不是访问授权。
十九、迟到下载不能强制跳转
用户等待时可能离开页面或切换账号。下载完成回调先比对 request owner;已失效就只缓存模块可用状态,不自动把目标页面推到当前栈顶。
二十、升级时治理参数版本
入口模块与 HSP 可能分批升级。DTO 携带 schemaVersion,目标模块只接受兼容范围;不兼容时进入安全错误态。不要根据字段"看起来存在"就继续运行。
二十一、路由名称不能随模块重构漂移
移动源码文件时可以更新 pageSourceFile,但稳定页面名应尽量保留。确需改名时提供显式迁移表,恢复数据和深链先转换旧名称,再提交新目标。
二十二、恢复栈要过滤缺失模块
持久化恢复前检查每个页面名称是否仍注册、参数版本是否兼容、目标模块是否可用。缺失按需模块不要在冷启动时静默下载并阻塞首屏,可先恢复安全主页,再由用户明确触发。
二十三、深链映射不能直通router_map
外部 URL 路径先经过域名、路径、参数白名单和登录态校验,再映射到本地路由名。攻击者提供的任意 name 不能直接传给 NavPathStack。
二十四、HAR与HSP的职责要区分
HAR 常用于共享代码与资源,HSP 可承载共享业务能力,HAP 是应用包模块。具体页面是否能作为动态目标取决于工程形态和官方支持,不应仅凭目录名称推断运行能力。
二十五、预览器结果不能代表系统路由表
官方跨包资料指出系统路由表支持模拟器但不支持预览器。预览器能显示页面组件,不代表 router_map、跨包加载或真实路由过程已经验证。验收需要模拟器或设备以及真实构建产物。
二十六、可观测字段要脱敏
记录 routeAlias、moduleAlias、准备阶段、耗时、来源枚举和结果;不记录完整深链、业务对象、令牌或用户内容。下载和路由事件用同一 operationId 关联,但对外上报采用受控散列。
二十七、无障碍和布局仍由页面负责
跨包成功只说明目标被构建并入栈。标题、返回按钮、焦点、深浅色、横竖屏、分栏和加载态需要目标模块独立验收。按需下载进度也应提供可读文本,不能只显示无标签动画。

二十八、纯策略模型的范围
配套 CrossPackageRoutePolicy.ts 按空名称、栈所有权、注册状态、模块准备和网络条件裁决允许、准备模块或拒绝。32项主机测试覆盖优先级、离线已安装和离线缺失。
模型不解析 router_map、不下载 HSP、不实例化 NavPathStack,也不证明系统跨包跳转成功。
二十九、静态检查示例
ts
interface RouteMapEntry { name:string; pageSourceFile:string; buildFunction:string }
function assertUnique(entries: RouteMapEntry[]): void {
const names = new Set<string>()
for (const entry of entries) {
if (names.has(entry.name)) throw new Error(`duplicate route: ${entry.name}`)
names.add(entry.name)
}
}
真实脚本还要验证路径、模块契约和 JSON 格式。
三十、真实工程验收矩阵
- 同包、跨 HAP/HSP/HAR 的允许路径;
- 名称冲突、路径错误和 Builder 缺失;
- 已安装模块离线跳转;
- 按需模块在线成功、离线失败和下载中断;
- 重复下载与重复入栈合并;
- 下载期间离场和切换账号;
- 恢复数据包含旧名称或缺失模块;
- 父子 NavPathStack 所有权;
- 模拟器与真机运行;
- 焦点、返回、多设备和深浅色。
三十一、常见问题排查
| 现象 | 常见原因 | 处理 |
|---|---|---|
| 目标页空白 | Builder 或根节点不正确 | 核对导出和NavDestination |
| 找不到页面 | router_map未注册或名称漂移 | 对照模块配置与契约 |
| 离线已安装页也被拦截 | 把联网当统一前置条件 | 区分模块准备状态 |
| 下载完成突然跳页 | 未校验迟到owner | 只缓存模块状态 |
| 两个容器串栈 | 全局复用NavPathStack | 一容器一栈 |
三十二、官方边界与验证声明
三十三、三十二点一、模块卸载与缓存失效要可恢复
按需模块曾经准备成功,不代表以后始终存在。用户清理空间、版本升级或安装状态变化后,本地"已就绪"缓存可能过期。每次关键跳转都要通过模块服务获取当前可用性,而不是永久相信一个布尔值;检测到失效时清理缓存并重新进入准备或回落流程。
恢复栈中如果包含已卸载模块,应用先保留安全主页和可用页面,再向用户解释缺失功能。不能为了复原历史栈强制下载,也不能让一个缺失页面阻断整个首屏。模块重新可用后,由用户明确操作进入目标,避免迟到恢复改变当前上下文。
三十四、三十二点二、路由契约变更需要兼容窗口
跨包独立演进时,调用方可能先于目标模块升级。新增参数应保持可选并有默认语义,删除或改名字段要提供迁移窗口。目标模块遇到更高版本 DTO 时应拒绝并回落,而不是忽略未知字段后执行可能错误的业务。
契约版本、模块版本和路由表版本应进入脱敏诊断事件。只有构建、安装和跨版本组合测试通过,才能缩短兼容窗口或移除旧映射。
华为官方跨包路由与页面路由资料说明了系统/自定义路由表、各模块 router_map 配置、NavPathStack 路由调用、API 23 起按需 HSP 页面支持及模拟器/预览器边界。具体能力以目标 SDK 文档为准。
本文未执行 HarmonyOS API 26 工程编译、router_map 系统解析、HSP 下载、跨包 Builder 构建、ArkUI 联调、模拟器或真机验收,不把32项主机策略测试描述成平台结果。
三十五、总结
跨包路由的可靠链路是:稳定名称与版本化参数形成契约,router_map 完成模块映射,ModuleService 管准备,RouteCoordinator 管所有权和去重,NavPathStack 管真实入栈,失败回落保留来源上下文。只有把下载和路由分成两个检查点,模块化才能既解耦又可诊断。
三十六、AI辅助声明
三十七、接入前检查清单
HarmonyOS AP正式接入前,工程侧要过一遍检查清单:SDK 版本与目标 API 版本匹配、权限在清单中声明且按需申请、能力开关在专用配置层管理、包体积增量符合预算。任何一项不过就先停,带着不匹配的版本往上冲,排错成本会翻倍。
ts
interface PrecheckItem { name: string; pass: boolean; note?: string }
const checklist: PrecheckItem[] = [
{ name: 'SDK/API版本匹配', pass: false },
{ name: '权限声明完整', pass: false },
{ name: '开关集中管理', pass: false },
{ name: '包体积增量<预算', pass: false },
]
const allPass = checklist.every(x => x.pass)
检查清单跑通的标志是:新建工程按清单接入一次成功,且不依赖任何口头经验。做不到,说明文档还缺关键步骤。
三十八、常见坑与版本适配记录
HarmonyOS AP最常踩的坑集中在三处:示例代码用的旧接口在新版本被拆分、默认配置与生产预期不一致、调试环境与真机行为有差异。每踩一个坑就记进版本适配表,注明坑点、影响版本、修复方式与验证方式。
ts
interface Pitfall { issue: string; sinceApi: string; fix: string; verifiedOn: string[] }
const pitfalls: Pitfall[] = []
function knownPit(api: string): Pitfall[] { return pitfalls.filter(p => p.sinceApi === api) }
适配表的价值在升级时兑现:每次目标版本变更,先对照表过一遍已知坑,能省下一整轮盲目排查。