前言
在 HarmonyOS ArkTS 声明式开发范式中,路由表是页面注册与跳转的中枢神经。它以一个简单的 JSON 文件描述了应用中所有可访问的页面,配合router.pushUrl、router.replaceUrl等 API 完成页面间导航。
本文将以开源鸿蒙笔友通信应用 xiexin 的main_pages.json为蓝本,详细剖析路由表的格式、与module.json5的契约关系、windowStage.loadContent如何依赖路由表,以及路由表设计中的常见陷阱。
提示:本文假设你已经了解 HarmonyOS Stage 模型基础。如果还不熟悉,建议先阅读前两篇文章。
一、main_pages.json 的定位
main_pages.json是 ArkTS 声明式开发范式下的页面路由表,它告诉系统:“我的应用里有哪些页面可以被加载”。
这个文件位于:
entry/src/main/resources/base/profile/main_pages.json路径解析如下:
| 路径段 | 含义 |
|---|---|
entry/src/main | 主模块源码根目录 |
resources | 资源目录 |
base | 默认资源限定(无特殊配置时的资源目录) |
profile | profile 类型的资源,用于存放 JSON 配置 |
main_pages.json | 文件名,可自定义但需在 module.json5 中引用 |
提示:除了
base目录,资源还可以放在dark、zh_CN、en_US等限定目录中。系统会根据设备状态自动选择匹配的资源。这部分内容将在后续文章中详细讲解。
二、xiexin 的路由表完整内容
xiexin 的main_pages.json内容非常简洁:
{"src":["pages/Index","pages/SplashPage","pages/ComposePage","pages/ReadLetterPage","pages/PenPalDetailPage","pages/AddPenPalPage","pages/StatsPage","pages/EditProfilePage"]}整个文件只有两个字段:
src:字符串数组,列出所有页面路径- (隐式)文件位置:决定了路由表如何被 module.json5 引用
三、src 数组中的路径约定
src数组中的每个字符串代表一个页面路径。这个路径有几条重要约定:
3.1 路径前缀pages/
路径中的pages/前缀对应文件系统中的实际位置:
entry/src/main/ets/pages/Index.ets ^^^^^^^^^^^^^^^^^ 对应路由表中的 "pages/Index"注意几个细节:
- 省略
.ets后缀:路由表中不写.ets,系统自动补全 - 路径分隔符:使用正斜杠
/,即使在 Windows 上也保持一致 - 大小写敏感:
pages/Index和pages/index是不同的页面 - 路径无
ets/前缀:因为ets已经是默认源码根目录
3.2 路径与 @Entry 装饰器的对应
src数组中的每个路径必须对应一个使用@Entry装饰的 ArkTS 文件。以pages/Index为例:
// entry/src/main/ets/pages/Index.ets@Entry@Componentstruct Index{@StatecurrentTab:number=0;build(){Tabs({barPosition:BarPosition.End,index:this.currentTab}){// ...}}}注意以下几点:
- 一个文件只能有一个
@Entry:@Entry标识"这是路由表入口",多入口会引发编译错误 @Entry必须搭配@Component:@Entry修饰的struct必须同时用@Component修饰struct名称可以任意:与路由路径无关,但建议与文件名保持一致以便维护
3.3 路径顺序与首屏加载
src数组中的第一个路径默认是应用的首屏。但 xiexin 的路由表第一个是pages/Index:
{"src":["pages/Index",// 首屏"pages/SplashPage",// ...]}这看起来与"应用启动时先看到 SplashPage"的设计矛盾。实际上,首屏加载由EntryAbility控制:
// entry/src/main/ets/entryability/EntryAbility.etsonWindowStageCreate(windowStage:window.WindowStage):void{windowStage.loadContent('pages/Index',(err)=>{// ...});}loadContent('pages/Index')显式指定加载pages/Index,而不是默认的pages/SplashPage。这是 xiexin 的一个设计取舍:
- 方案 A:首屏加载
pages/SplashPage,引导结束后跳转到pages/Index - 方案 B:首屏直接加载
pages/Index,引导页通过条件渲染内嵌
xiexin 当前采用了方案 A的变体:路由表首项是pages/Index,但Index内部会根据hasSeenSplash标志决定是否显示引导内容。这种设计避免了启动时的一次页面跳转,提升了首屏速度。
四、路由表与 module.json5 的契约
main_pages.json不是孤立的配置文件,它通过module.json5的pages字段被引用:
// entry/src/main/module.json5 { "module": { "name": "entry", "type": "entry", "mainElement": "EntryAbility", "deviceTypes": ["phone", "tablet", "2in1"], "pages": "$profile:main_pages", "abilities": [/* ... */] } }pages字段的值$profile:main_pages是一个资源引用,解析规则如下:
| 引用格式 | 含义 |
|---|---|
$profile:main_pages | 引用resources/base/profile/main_pages.json |
$media:app_icon | 引用resources/base/media/app_icon.png |
$string:app_name | 引用resources/base/element/string.json中的app_name |
$color:start_window_background | 引用resources/base/element/color.json中的颜色 |
提示:
$profile:main_pages中的main_pages是文件名(不含.json后缀),$profile:是资源类型前缀。系统会在resources/<qualifier>/profile/目录下查找匹配的 JSON 文件。
五、路由表的加载时机
理解路由表的加载时机,对于排查"页面找不到"问题至关重要。整个加载过程分为三个阶段:
5.1 编译阶段
在工程编译时,hvigor 工具会扫描module.json5中的pages字段,找到对应的main_pages.json,然后:
- 校验路径合法性:检查每个路径是否对应真实的
.ets文件 - 生成路由映射表:将路径字符串映射到编译后的字节码位置
- 注入路由元数据:把映射表打包进 HAP 文件
如果某个路径对应的文件不存在,编译时会报错:
ERROR: Bundle 'pages/NotExist' is not found in routes.5.2 安装阶段
HAP 文件被安装到设备后,系统会在首次启动应用时读取路由映射表,并预加载页面元数据。这一步通常很快,但页面代码本身不会立即加载。
5.3 运行时加载
当调用router.pushUrl({ url: 'pages/ComposePage' })或windowStage.loadContent('pages/Index')时,系统才会:
- 查找路由映射:根据路径字符串找到对应的字节码位置
- 加载字节码:动态加载对应页面的字节码到 ArkTS 运行时
- 实例化组件:调用
@Entry修饰的struct构造函数 - 触发
aboutToAppear:执行组件初始化逻辑 - 执行
build:生成 UI 树并渲染
这种"按需加载"的设计有两个好处:
- 减少首屏内存占用:未访问的页面字节码不加载
- 加速冷启动:只加载首屏需要的代码
提示:如果你想进一步优化冷启动,可以使用 ArkTS 的
lazy import语法延迟加载非首屏模块的代码。
六、路由表的扩展实践
让我们看看如何在 xiexin 中扩展一个新的设置页面。
6.1 创建页面文件
// entry/src/main/ets/pages/SettingsPage.ets@Entry@Componentstruct SettingsPage{@StatedarkMode:boolean=false;build(){Column({space:16}){Text('设置').fontSize(24).fontWeight(FontWeight.Bold)Row(){Text('深色模式').fontSize(16)Toggle({type:ToggleType.Switch,isOn:this.darkMode}).onChange((isOn:boolean)=>{this.darkMode=isOn;AppStorage.setOrCreate('darkMode',isOn);})}.width('100%').justifyContent(FlexAlign.SpaceBetween).padding(16).backgroundColor(AppColors.WHITE).borderRadius(12)}.height('100%').backgroundColor(AppColors.PRIMARY_BG).padding(16)}}6.2 注册路由
{"src":["pages/Index","pages/SplashPage","pages/ComposePage","pages/ReadLetterPage","pages/PenPalDetailPage","pages/AddPenPalPage","pages/StatsPage","pages/EditProfilePage","pages/SettingsPage"]}6.3 跳转到设置页
import{router}from'@kit.ArkUI';// 在 Index 页面添加设置入口Button('设置').onClick(()=>{router.pushUrl({url:'pages/SettingsPage'});});这样就完成了一个新页面的接入。
七、路由表设计的常见陷阱
7.1 路径拼写错误
{"src":["page/Index"]}错误:page应为pages。这种错误在编译期不会被捕获,但运行时调用loadContent会失败。
7.2 忘记更新路由表
新增了一个ComposePage.ets文件,但忘记在路由表中添加"pages/ComposePage"。结果是:
- 编译通过:文件本身被编译进 HAP
- 运行时失败:
router.pushUrl({ url: 'pages/ComposePage' })报错"路由不存在"
提示:建议在 CI/CD 流程中加入"路由表校验"步骤,自动扫描
src/main/ets/pages/下的.ets文件,与main_pages.json比对是否一致。
7.3 路由表条目过多
随着业务增长,main_pages.json可能膨胀到几十甚至上百个条目。这本身不是问题,但会带来两个隐患:
- 首屏代码量增加:路由表本身不大,但页面越多,编译产物中元数据越多
- 维护成本上升:手动维护大列表容易遗漏
解决方案是采用模块化路由:把不同业务模块的路由表拆分到不同 profile 文件,在module.json5中按需引用。
7.4 大小写敏感问题
{"src":["pages/index"]}错误:文件实际是Index.ets(首字母大写),路由路径却写成index。在 Linux/macOS 文件系统上可能不报错,但路由查找时找不到匹配项。
八、main_pages.json 与路由跳转 API 的协作
理解路由表后,我们来看看它如何与 ArkUI 的routerAPI 协作。
8.1 router.pushUrl
router.pushUrl({url:'pages/ComposePage'});pushUrl会将目标页面压入路由栈,当前页面保留在栈底。用户点击返回键时,会自动出栈,回到之前的页面。
8.2 router.replaceUrl
router.replaceUrl({url:'pages/Index'});replaceUrl会替换当前页面,原页面从栈中移除。这种跳转方式常用于"启动引导页跳转主页"的场景——引导页不应该出现在返回栈里。
xiexin 的SplashPage就采用了这种模式:
// SplashPage 的"开始写信"按钮Button('开始写信').onClick(()=>{router.replaceUrl({url:'pages/Index'});})这样用户从 SplashPage 进入 Index 后,按返回键不会回到 SplashPage,而是直接退出应用。
8.3 router.pushUrl 带参数
router.pushUrl({url:'pages/PenPalDetailPage',params:{id:123}});目标页面通过router.getParams()获取参数:
@Entry@Componentstruct PenPalDetailPage{@StatepenPalId:number=0;aboutToAppear():void{constparams=router.getParams()asRecord<string,number>;this.penPalId=params.id;}build(){/* ... */}}提示:
router.getParams()必须在aboutToAppear或之后的生命周期调用,在build之外的其他时机可能返回undefined。
8.4 router.back 返回上一页
// 返回上一页router.back();// 返回指定页面(清除中间页面)router.back({url:'pages/Index'});router.back({ url: 'pages/Index' })会一直出栈,直到遇到pages/Index。如果栈中没有这个页面,调用无效。
九、路由栈深度管理
HarmonyOS 的路由栈有最大深度限制(默认 32 层)。如果应用业务复杂,可能触发栈溢出:
Error: The route stack exceeds the maximum limit.管理路由栈深度的几个建议:
- 用
replaceUrl替代pushUrl:当不需要保留历史页面时,用 replace 避免栈增长 - 用
router.clear()清栈:在退出登录等场景清空整个路由栈 - 用
router.back({ url: '...' })深度返回:避免逐层 pop
十、main_pages.json 的进阶用法
10.1 多 profile 文件
module.json5的pages字段只能引用一个 profile 文件,但这个文件可以包含多个 src 数组:
{"src":["pages/Index","pages/SplashPage"],"src-extension":["pages/ComposePage","pages/ReadLetterPage","pages/PenPalDetailPage","pages/AddPenPalPage","pages/StatsPage","pages/EditProfilePage"]}这种写法允许按业务场景组织页面,但实际加载时仍会合并所有src*数组中的路径。
10.2 路由表与动态加载
对于大型应用,可以把"按需加载"的页面放在独立的 HAR/HSP 模块中,每个模块有自己的路由表。这种"模块化路由"是 HarmonyOS 多模块架构的关键能力。
总结
本文详细剖析了 HarmonyOS ArkTS 路由表main_pages.json的格式、契约关系、加载机制和扩展实践。我们看到 xiexin 的路由表虽然只有 8 个条目,却完整覆盖了笔友通信场景的所有页面,体现了"小而美"的设计哲学。
理解路由表的关键是把握"四个一"原则:一个 src 数组、一个路径约定、一个 module.json5 引用、一个 EntryAbility 首屏加载。这四个环节环环相扣,共同构成了 HarmonyOS 应用页面注册与跳转的基础设施。
下一篇文章我们将深入module.json5,剖析模块能力声明、权限配置、abilities 数组等核心配置项。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源
- 开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
- HarmonyOS 应用配置文件概述:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/application-configuration-file-overview-stage
- HarmonyOS module.json5 配置:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/module-configuration-file
- HarmonyOS 资源分类与访问:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/resource-categories-and-access
- HarmonyOS 路由 Router 开发指导:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-routing
- HarmonyOS ArkTS 声明式开发基础语法:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-basic-syntax-overview
- HarmonyOS 应用程序包结构:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/application-package-structure-stage
- HarmonyOS ArkUI router API:https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-arkui-router