HarmonyOS HMRouter 接入记录:从普通 Tab Demo 到路由跳转
前言
今天在鸿蒙 Demo 中接入了 @hadss/hmrouter 路由库。
一开始只是用 @Local currentIndex 控制底部 Tab 切换:
txt
点击底部按钮
↓
修改 currentIndex
↓
if / else 展示不同组件
这种方式本质是组件切换,不是页面路由。
后来改成使用 HMRouter:
txt
点击底部按钮
↓
HMRouterMgr.push()
↓
通过路由切换页面
这篇文章记录完整接入流程,以及遇到的问题,方便以后排查。
一、最终实现目标
Demo 有三个页面:
txt
首页
Home
Setting
底部有一个导航栏,点击不同 Tab 时,通过 HMRouter 路由跳转到对应页面。
最终结构:
txt
Index.ets
│
├── HMNavigation 路由容器
│
└── 底部 TabBar
├── 首页 -> pages/TabHome
├── Home -> pages/Home
└── Setting -> pages/Setting
二、目录结构
示例目录:
txt
MyApplication
├── AppScope
│ └── app.json5
├── entry
│ ├── hvigorfile.ts
│ ├── hmrouter_config.json
│ └── src
│ └── main
│ ├── ets
│ │ ├── entryability
│ │ │ └── EntryAbility.ets
│ │ └── pages
│ │ ├── Index.ets
│ │ ├── TabHome.ets
│ │ ├── Home.ets
│ │ └── Setting.ets
│ └── module.json5
├── hvigor
│ └── hvigor-config.json5
├── hvigorfile.ts
└── oh-package.json5
三、接入 HMRouter 的完整流程
HMRouter 接入主要分 8 步:
txt
1. 安装运行库
2. 配置 hvigor 插件依赖
3. 在 entry/hvigorfile.ts 启用插件
4. 新建 hmrouter_config.json 指定扫描目录
5. EntryAbility 初始化 HMRouterMgr
6. 页面使用 @HMRouter 注册
7. Index 使用 HMNavigation 承载路由页面
8. 点击底部 Tab 使用 HMRouterMgr.push 跳转
四、第一步:安装运行库
在项目根目录执行:
bash
ohpm install @hadss/hmrouter
这个包提供运行时代码:
ts
HMNavigation
HMRouter
HMRouterMgr
如果没有安装,页面里 import 会失败。
五、第二步:配置插件依赖
打开:
txt
hvigor/hvigor-config.json5
添加:
json5
{
"modelVersion": "6.1.0",
"dependencies": {
"@hadss/hmrouter-plugin": "^1.2.2"
}
}
注意:
txt
@hadss/hmrouter 是运行库,用 ohpm 安装
@hadss/hmrouter-plugin 是编译插件,写在 hvigor-config.json5
不要执行:
bash
ohpm install @hadss/hmrouter-plugin
否则可能 404。
六、第三步:entry/hvigorfile.ts 启用插件
打开:
txt
entry/hvigorfile.ts
写:
ts
import { hapTasks } from '@ohos/hvigor-ohos-plugin'
import { hapPlugin } from '@hadss/hmrouter-plugin'
export default {
system: hapTasks,
plugins: [
hapPlugin()
]
}
这一步非常关键。
如果没有启用插件,@HMRouter 不会被扫描,也不会生成路由表。
七、第四步:新建 hmrouter_config.json
在 entry 目录下新建:
txt
entry/hmrouter_config.json
内容:
json
{
"scanDir": ["src/main/ets/pages"],
"saveGeneratedFile": true
}
作用:
txt
告诉 HMRouter 插件去哪里扫描 @HMRouter 页面
因为页面在:
txt
entry/src/main/ets/pages
所以 scanDir 写:
txt
src/main/ets/pages
注意这个文件位置:
txt
entry/hmrouter_config.json
不是:
txt
项目根目录/hmrouter_config.json
八、第五步:EntryAbility 初始化 HMRouterMgr
打开:
txt
entry/src/main/ets/entryability/EntryAbility.ets
引入:
ts
import { HMRouterMgr } from '@hadss/hmrouter'
在 onCreate 中初始化:
ts
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
HMRouterMgr.openLog('INFO')
HMRouterMgr.init({
context: this.context
})
}
如果没有初始化,运行时可能报:
txt
ERR_INIT_NOT_READY
Framework initialization not completed
九、第六步:页面使用 @HMRouter 注册
TabHome.ets
ts
import { HMRouter } from '@hadss/hmrouter'
@HMRouter({ pageUrl: 'pages/TabHome' })
@ComponentV2
export struct TabHome {
build() {
Column() {
Text('首页')
.fontSize(30)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
Home.ets
ts
import { HMRouter } from '@hadss/hmrouter'
@HMRouter({ pageUrl: 'pages/Home' })
@ComponentV2
export struct HomeBuilder {
build() {
Column() {
Text('Home 页面')
.fontSize(30)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
Setting.ets
ts
import { HMRouter } from '@hadss/hmrouter'
@HMRouter({ pageUrl: 'pages/Setting' })
@ComponentV2
export struct Setting {
build() {
Column() {
Text('Setting 页面')
.fontSize(30)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
十、第七步:Index.ets 使用 HMNavigation
Index.ets 是入口页面。
ts
import { HMNavigation, HMRouterMgr } from '@hadss/hmrouter'
@Entry
@ComponentV2
struct Index {
@Local currentTab: string = 'pages/TabHome'
build() {
Column() {
HMNavigation({
navigationId: 'MainNavigation',
homePageUrl: 'pages/TabHome'
})
.layoutWeight(1)
.width('100%')
Row() {
this.TabItem('首页', 'pages/TabHome')
this.TabItem('Home', 'pages/Home')
this.TabItem('Setting', 'pages/Setting')
}
.width('100%')
.height(60)
.backgroundColor('#FFFFFF')
}
.width('100%')
.height('100%')
}
@Builder
TabItem(title: string, pageUrl: string) {
Column() {
Text(title)
.fontSize(14)
.fontColor(this.currentTab === pageUrl ? '#007DFF' : '#666666')
}
.layoutWeight(1)
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.onClick(() => {
this.currentTab = pageUrl
HMRouterMgr.push({
pageUrl: pageUrl
})
})
}
}
十一、整体流程图
txt
应用启动
│
▼
EntryAbility.onCreate()
│
├── HMRouterMgr.openLog()
└── HMRouterMgr.init()
│
▼
Index.ets
│
├── HMNavigation
│ └── homePageUrl: pages/TabHome
│
└── 底部 TabBar
│
├── 点击 首页
│ └── HMRouterMgr.push({ pageUrl: 'pages/TabHome' })
│
├── 点击 Home
│ └── HMRouterMgr.push({ pageUrl: 'pages/Home' })
│
└── 点击 Setting
└── HMRouterMgr.push({ pageUrl: 'pages/Setting' })
十二、每一步的作用总结
| 步骤 | 作用 |
|---|---|
ohpm install @hadss/hmrouter |
安装运行库 |
hvigor-config.json5 配置 plugin |
让 hvigor 能下载编译插件 |
entry/hvigorfile.ts 启用 hapPlugin() |
让插件真正执行 |
entry/hmrouter_config.json |
指定扫描页面目录 |
EntryAbility.ets 初始化 |
初始化路由框架 |
@HMRouter |
注册页面 |
HMNavigation |
路由容器 |
HMRouterMgr.push |
跳转页面 |
十三、常见问题记录
1. ERR_INIT_NOT_READY
错误:
txt
[HMRouter ERROR] ERR_INIT_NOT_READY 40001003
Framework initialization not completed
原因:
txt
HMRouterMgr 没有初始化
解决:
在 EntryAbility.ets 的 onCreate 中添加:
ts
HMRouterMgr.openLog('INFO')
HMRouterMgr.init({
context: this.context
})
2. not exist pageUrl in store
错误:
txt
[HMRouter INFO][HMRouterStore] not exist pageUrl in store, pageUrl is pages/Home
原因:
txt
路由表里没有这个页面
常见原因:
txt
1. 页面没有写 @HMRouter
2. pageUrl 不一致
3. hmrouter-plugin 没有执行
4. scanDir 没覆盖页面目录
5. 没有 Clean / Rebuild
6. 在 Previewer 里运行
解决排查:
txt
1. 确认页面有 @HMRouter({ pageUrl: 'pages/Home' })
2. 确认 push 的 pageUrl 完全一致
3. 确认 entry/hvigorfile.ts 配置 hapPlugin()
4. 确认 entry/hmrouter_config.json 位置正确
5. Clean Project + Rebuild Project
6. Run 到模拟器,不要只用 Previewer
3. 搜不到 hm_router_map.json
如果搜不到生成的路由表,说明插件没有成功生成路由映射。
排查:
txt
1. hmrouter_config.json 是否在 entry 目录下
2. scanDir 是否写对
3. entry/hvigorfile.ts 是否启用 hapPlugin()
4. 是否 Rebuild Project
可以在 DevEco 中:
txt
Ctrl + Shift + F
搜索:
txt
pages/Home
如果生成成功,除了自己写的页面代码外,还应该能搜到生成文件中的路由信息。
4. Previewer 运行异常
日志中如果出现:
txt
previewer is a mocked implementation
说明当前是在 Previewer 中运行。
HMRouter 依赖编译产物、rawfile 和运行时能力,建议:
txt
不要用 Previewer 验证 HMRouter
要 Run 到模拟器或真机
5. 安装失败:install sign info inconsistent
错误:
txt
Install Failed: error: failed to install bundle.
code:9568332
error: install sign info inconsistent.
原因:
txt
模拟器里有旧包残留
旧包签名和新包签名不一致
解决:
先查设备:
powershell
& "C:\Program Files\Huawei\DevEco Studio\sdk\default\openharmony\toolchains\hdc.exe" list targets
输出类似:
txt
127.0.0.1:5555
卸载旧包:
powershell
& "C:\Program Files\Huawei\DevEco Studio\sdk\default\openharmony\toolchains\hdc.exe" -t 127.0.0.1:5555 uninstall com.example.myapplication
然后重新运行。
十四、调试经验总结
这次接入 HMRouter 之后,发现鸿蒙路由库的问题不一定是页面代码问题。
更多时候问题出在:
txt
1. 编译插件没有执行
2. 路由表没有生成
3. pageUrl 不一致
4. 没有初始化路由框架
5. 使用 Previewer 而不是模拟器
6. 模拟器旧包签名冲突
所以以后遇到白屏,优先看:
txt
HiLog 里的 HMRouter 日志
Build Output 里的插件执行日志
是否生成 hm_router_map.json
是否 Run 到模拟器
十五、最终理解
HMRouter 使用起来其实不复杂,真正复杂的是第一次接入时的工程配置。
可以简单记成一句话:
txt
ohpm 装运行库
hvigor 配插件
hmrouter_config 指定扫描目录
EntryAbility 初始化
@HMRouter 注册页面
HMNavigation 承载页面
HMRouterMgr.push 跳转页面
跑通一次之后,后面基本就是复制模板。
十六、组件切换 vs 路由跳转
组件切换
txt
@Local currentIndex
↓
if / else
↓
显示不同组件
优点:
txt
简单,适合练习状态和组件通信
缺点:
txt
不是真正页面路由
没有路由栈
不适合复杂页面跳转
HMRouter 路由跳转
txt
@HMRouter 注册页面
↓
HMNavigation 承载
↓
HMRouterMgr.push 跳转
优点:
txt
是真正页面路由
支持页面栈
适合企业项目
十七、后续计划
下一步可以继续练:
txt
1. 路由传参
2. HMRouterMgr.pop 返回
3. replace 跳转
4. 页面生命周期
5. 路由拦截
6. 结合商品管理 Demo 做详情页
比如:
txt
商品列表页
↓
点击商品
↓
路由跳转到商品详情页
↓
传递商品 id
这样更接近真实业务。