📖 前言

在第一篇中,我们已经完成了 HMRouter 类型封装HMRouterUtil 工具类 的搭建,让路由调用在代码层面变得更简洁。本篇我们继续往下走,聚焦于三个实战主题:

  • HMRouter 编译插件配置:让 @HMRouter 注解真正生效
  • HMRouter 初始化与根容器配置:解决“白屏”“路由不生效”等常见坑
  • 测试页面编写:用一组示例页面,把常用 API 全部跑一遍

本文完全基于实际工程 MyHMRouter,你可以对照代码一步一步完成。


一、HMRouter 编译插件配置

HMRouter 的 @HMRouter 注解并不是“自动生效”的,它依赖 编译期插件 去扫描注解、生成路由表和构建器。如果不配置插件,即使你在页面上加了 @HMRouter,运行时也会出现:

  • 页面打不开
  • 直接白屏
  • 控制台无明显报错

1.1 在 hvigor-config.json5 中声明插件依赖

文件: hvigor/hvigor-config.json5

dependencies 中加入 HMRouter 插件:

{
  "modelVersion": "6.0.2",
  "dependencies": {
    "@hadss/hmrouter-plugin": "latest"
  },
  "execution": {
    // 这里可以配置编译相关选项
  }
}

要点:

  • 版本直接使用 "latest",方便后续跟随官方更新
  • 这是 项目级 配置,所有模块都会看到这个插件依赖

1.2 在根 hvigorfile.ts 中启用 appPlugin

文件: hvigorfile.ts

import { appTasks } from '@ohos/hvigor-ohos-plugin';
import { appPlugin } from '@hadss/hmrouter-plugin';

export default {
  system: appTasks, /* Hvigor 内置插件 */
  plugins: [appPlugin({ ignoreModuleNames: [] })]  /* 启用 HMRouter app 级插件 */
}

几点说明:

  • 必须传入参数对象,否则会出现:
    • No options provided 之类的错误
  • ignoreModuleNames: [] 表示:
    • 不忽略任何模块,所有模块都参与 HMRouter 插件处理

1.3 在 entry 模块 hvigorfile.ts 中启用 hapPlugin

文件: entry/hvigorfile.ts

import { hapTasks } from '@ohos/hvigor-ohos-plugin';
import { hapPlugin } from '@hadss/hmrouter-plugin';

export default {
  system: hapTasks, /* Hvigor 内置插件 */
  plugins: [hapPlugin()]  /* 启用 HMRouter hap 级插件 */
}

作用:

  • entry 这个 HAP 模块参与 HMRouter 的编译过程
  • 插件会在编译时扫描 @HMRouter,生成路由表和页面构建代码

1.4 常见问题排查

  • 忘记配置插件依赖
    • 现象:@HMRouter 加上了,但页面死活跳不过去
    • 解决:检查 hvigor/hvigor-config.json5 中是否有 "@hadss/hmrouter-plugin"
  • appPlugin 未传 options
    • 现象:构建时报 APP_HMROUTER_PLUGIN 相关错误
    • 解决:务必写成 appPlugin({ ignoreModuleNames: [] })
  • 只配置了根 hvigorfile.ts,忘了 entry/hvigorfile.ts
    • 现象:项目能编译,但路由白屏
    • 解决:确认 entry/hvigorfile.ts 中已添加 hapPlugin()

二、HMRouter 初始化与根容器配置

插件配置好之后,还需要在运行时做两件事:

  • 初始化 HMRouter(通常在 EntryAbility 中)
  • 使用 HMNavigation 作为根路由容器(替代直接渲染页面)

2.1 在 EntryAbility 中初始化 HMRouter

文件: entry/src/main/ets/entryability/EntryAbility.ets

核心代码:

import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { HMRouterUtil } from '../hmRouter/util/HMRouterUtil';

const DOMAIN = 0x0000;

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // 这里是系统模板代码,可保留

    // 初始化 HMRouter(在 onCreate 中初始化)
    HMRouterUtil.openLog('DEBUG');
    HMRouterUtil.init({
      context: this.context,
      logLevel: 'DEBUG'
    }).then(() => {
      hilog.info(DOMAIN, 'testTag', 'HMRouter init success');
    }).catch((err: Error) => {
      hilog.error(DOMAIN, 'testTag', 'HMRouter init failed: %{public}s', JSON.stringify(err));
    });
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    // 设置首个 UI 页面
    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
        return;
      }
      hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
    });
  }
}

关键点:

  • 初始化放在 onCreate 中,确保 Ability 启动时就完成 HMRouter 初始化
  • 使用 HMRouterUtil.init 而不是直接调用 HMRouterMgr,保持封装一致性
  • openLog('DEBUG') 在调试阶段非常有用,可以看到路由日志

2.2 配置 main_pages.json

文件: entry/src/main/resources/base/profile/main_pages.json

{
  "src": [
    "pages/Index"
  ]
}

说明:

  • src 至少要有一个页面,否则会出现 Schema 校验错误
  • 这里我们指定 pages/Index 作为入口页面,对应下面的 Index.ets

2.3 使用 HMNavigation 作为根容器

文件: entry/src/main/ets/pages/Index.ets

import { HMNavigation } from '@hadss/hmrouter';
import { AttributeUpdater } from '@kit.ArkUI';

/**
 * Navigation 属性修改器:用于隐藏导航栏
 */
class MyNavModifier extends AttributeUpdater<NavigationAttribute> {
  initializeModifier(instance: NavigationAttribute): void {
    instance.hideNavBar(true);
  }
}

@Entry
@Component
export struct Index {
  modifier: MyNavModifier = new MyNavModifier();

  build() {
    Column() {
      HMNavigation({
        navigationId: 'MainNavigation',
        homePageUrl: 'pages/HomePage',
        options: {
          modifier: this.modifier
        }
      })
    }
    .width('100%')
    .height('100%')
  }
}

设计说明:

  • Index 是整个应用的根组件(@Entry
  • 内部使用 HMNavigation 承载所有路由页面
  • homePageUrl 设置为 'pages/HomePage',即我们的测试主页面
  • navigationId 建议统一命名,例如 'MainNavigation',方便在代码中引用

2.4 白屏问题排查 checklist

如果你遇到 已启动就白屏,可以按下面顺序排查:

  1. main_pages.json 是否配置了 pages/Index?
  2. EntryAbility.onWindowStageCreate 是否 loadContent('pages/Index')?
  3. Index.ets 是否使用了 @Entry 和 HMNavigation?
  4. 是否已经配置 hmrouter 插件(hvigor-config.json5 / hvigorfile.ts / entry/hvigorfile.ts)?
  5. 是否在 EntryAbility.onCreate 中调用了 HMRouterUtil.init?

基本上这几项都正确后,白屏问题就能排除 90%。


三、构建一套完整的测试页面

为了验证封装是否可靠,我们在项目中创建了一套 覆盖常用 API 的测试页,并通过 HomePage 统一入口来访问。

3.1 HomePage:测试入口页

文件: entry/src/main/ets/pages/HomePage.ets

功能:

  • 提供七个按钮,分别跳转到:
    • 详情页
    • 参数测试页
    • Replace 测试页
    • 回调测试页
    • 链式调用测试页
    • 路由栈测试页
    • Options 参数测试页

关键代码节选:

@HMRouter({ pageUrl: 'pages/HomePage' })
@Component
export struct HomePage {
  @State message: string = 'HMRouter 测试主页';

  // 在 aboutToAppear 中初始化各种测试参数
  aboutToAppear() {
    this.pushParam = { id: 1, name: '跳转测试' };
    // ... 其他参数初始化
  }

  build() {
    Scroll() {
      Column() {
        Button('1. 跳转到详情页')
          .onClick(() => {
            if (this.pushParam) {
              HMRouterUtil.push('pages/DetailPage', this.pushParam as Object);
            }
          })
        // ... 其他按钮
      }
    }
  }
}

3.2 详情页:测试基本跳转与返回

文件: DetailPage.ets

  • 使用 getCurrentParam(HMRouterUtilParamType.all) 获取传入参数
  • 使用 setPopParam + pop 返回上一页并携带结果
@HMRouter({ pageUrl: 'pages/DetailPage' })
@Component
export struct DetailPage {
  @State paramInfo: string = '';

  aboutToAppear() {
    const param = HMRouterUtil.getCurrentParam(HMRouterUtilParamType.all);
    this.paramInfo = param ? JSON.stringify(param) : '无参数';
  }

  build() {
    Button('返回上一页')
      .onClick(() => {
        HMRouterUtil.setPopParam({ result: '从详情页返回', success: true } as Object);
        HMRouterUtil.pop();
      })
  }
}

3.3 参数测试页:验证 HMParamType

文件: ParamPage.ets

  • 分别获取:
    • 所有参数:HMRouterUtilParamType.all
    • URL 参数:HMRouterUtilParamType.urlParam
    • 路由参数:HMRouterUtilParamType.routeParam

注意:

  • 枚举值必须使用 urlParam / routeParam,而不是 url / route,否则会有编译错误。

3.4 Replace 测试页:replace / replaceAsync

文件: ReplaceTestPage.etsReplaceResultPage.ets

  • ReplaceTestPage
    • 同时测试:replacereplaceAsync
    • 说明 Replace 会“跳过当前页返回”,验证路由栈行为
  • ReplaceResultPage
    • 一个简单结果页,点击按钮 pop() 返回

3.5 回调测试页:onArrival / onResult / onLost

文件: CallbackTestPage.etsCallbackResultPage.ets

  • CallbackTestPage 中:
    • 构造 HMRouterUtilPathCallback 对象,记录回调日志
    • 使用 HMRouterUtil.push('pages/CallbackResultPage', param, undefined, callback) 进行跳转
  • CallbackResultPage 中:
    • 使用 setPopParam 设置返回参数,pop() 触发上一个页面的 onResult

3.6 链式调用测试页:to().withParam().push()

文件: ChainTestPage.etsChainResultPage.ets

示例:

HMRouterUtil.to('pages/ChainResultPage')
  .withParam(this.chainParam1 as Object)
  .withNavigation('MainNavigation')
  .push();

await HMRouterUtil.to('pages/ChainResultPage')
  .withParam(this.chainParam2 as Object)
  .pushAsync();

3.7 路由栈测试页:getPathStack / existUrl

文件: StackTestPage.etsStackTestPage2.ets

  • 使用 getPathStack('MainNavigation') 查看当前导航栈深度和页面列表
  • 使用 existUrl('pages/HomePage') / existUrl('pages/NotExistPage') 检查页面是否存在

3.8 Options 测试页:HMRouterUtilOptions

文件: OptionsTestPage.etsOptionsResultPage.ets

  • OptionsTestPage 中构造 HMRouterUtilOptions
    • navigationId: 'MainNavigation'
    • skipAllInterceptor: false
  • 调用:
HMRouterUtil.push('pages/OptionsResultPage', this.testParam as Object, this.options);

四、实战总结与建议

4.1 工程层面的最佳实践

  • 强制使用 HMRouterUtil
    • 在业务代码中禁止直接调用 HMRouterMgr,统一通过 HMRouterUtil
    • 方便后续升级、统一处理日志和错误
  • 统一 navigationId
    • 建议项目只维护少量(甚至一个)navigationId,统一为常量
  • 所有路由页面都使用 @HMRouter
    • 方便 HMRouter 生成路由表
    • 避免字符串硬编码过多

4.2 调试与排错建议

  • 优先看 HMRouter 日志
    • 使用 HMRouterUtil.openLog('DEBUG')
    • 观察跳转、拦截、返回等关键节点
  • 遇到 ArkTS 报错时的思路
    • arkts-no-untyped-obj-literals:为对象定义 interface,在 aboutToAppear() 中初始化
    • arkts-no-obj-literals-as-types:不要用对象字面量直接当类型
    • arkts-no-classes-as-obj:类不要当对象导出,使用 export { Class as Alias }

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐