惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

www.infosecurity-magazine.com
www.infosecurity-magazine.com
D
DataBreaches.Net
T
Tailwind CSS Blog
M
MIT News - Artificial intelligence
Stack Overflow Blog
Stack Overflow Blog
F
Full Disclosure
V2EX - 技术
V2EX - 技术
N
News and Events Feed by Topic
Help Net Security
Help Net Security
L
LangChain Blog
Y
Y Combinator Blog
宝玉的分享
宝玉的分享
Google Online Security Blog
Google Online Security Blog
P
Proofpoint News Feed
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
The Blog of Author Tim Ferriss
Google DeepMind News
Google DeepMind News
The Register - Security
The Register - Security
B
Blog RSS Feed
N
Netflix TechBlog - Medium
N
News | PayPal Newsroom
TaoSecurity Blog
TaoSecurity Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
V
Vulnerabilities – Threatpost
B
Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
I
Intezer
H
Hackread – Cybersecurity News, Data Breaches, AI and More
博客园_首页
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
AI
AI
aimingoo的专栏
aimingoo的专栏
大猫的无限游戏
大猫的无限游戏
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Cyberwarzone
Cyberwarzone
P
Proofpoint News Feed
Google DeepMind News
Google DeepMind News
G
GRAHAM CLULEY
Vercel News
Vercel News
罗磊的独立博客
MyScale Blog
MyScale Blog
Last Week in AI
Last Week in AI
博客园 - 司徒正美
C
CERT Recently Published Vulnerability Notes
GbyAI
GbyAI
Scott Helme
Scott Helme
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
T
Troy Hunt's Blog
A
About on SuperTechFans
P
Privacy International News Feed

开源实验室

一个月,纯VibeCoding,全平台云笔记APP|开源实验室 kymjs.com 借助 API 手写一个 Transformer 架构|开源实验室 PT 站规则扫盲|开源实验室 使用 RAGFlow 搭建一套 AI 客服知识库|开源实验室 终于搞定了,本地部署 RAGFlow|开源实验室 阿富汗-中亚最倒霉国家|开源实验室 可能是 AGP8 编译最快的方案|开源实验室 NAS - 玩点有意思的|开源实验室 动态路由 TheRouter 的设计与实践|开源实验室 没错,TheRouter 是我写的|开源实验室 货拉拉 Android 模块化路由框架:TheRouter|开源实验室 菲律宾这国家到底怎么回事|开源实验室 印度的穆斯林王朝——德里苏丹|开源实验室 学历史有什么用|开源实验室 印度的列国时代大一统——孔雀王朝|开源实验室 印度列国时代的百家争鸣——沙门思潮|开源实验室 印度真是个神奇的国度——总章|开源实验室 某一天,中国真会超过美国吗?|开源实验室 基于 C++ 的 Android 协程设计|开源实验室 Gradle 6.X 上传 aar 到 Nexus 私服|开源实验室 自定义C/C++日志输出函数|开源实验室 扁平化管理,就是管理者的失职|开源实验室 再聊 Git Flow|开源实验室 Flutter 设置控件是否可见|开源实验室 Flutter 线性布局:Column 和 Row|开源实验室 开源一段 Mac 批量压缩图片的脚本|开源实验室 Leader 的自我修养——学会预测|开源实验室 玩玩区块链——概念|开源实验室 一条电商 Android 工程化实践|开源实验室
【HarmonyOS】TheRouter 鸿蒙版新手入门|开源实验室
2025-03-23 · via 开源实验室

对本文有任何问题,可加我的个人微信询问:kymjs666

TheRouter 鸿蒙版是货拉拉基于HMRouter深度定制的开源路由框架,提供了 Android、iOS、Harmony 三端高一致性使用,在支持平台化应用实现组件化、跨模块调用、动态化等功能的集成等功能基础上,支持动态路由下发、编译时安全检查、路由Path一对多等高度动态能力。

Github: https://github.com/HuolalaTech/hll-wp-therouter-harmony/
官网:http://therouter.cn/

0、开始之前

如果你的是新项目,请先记住一点:plugin、router 两个依赖的版本号必须保持一致,请继续往下看接入步骤。

0.1、查看最新版本

TheRouter 的版本分为两种,稳定版和 rc版,一般不追求新功能我们就用稳定版就行,可以在官网看到最新的版本号和各种版本的说明:https://therouter.cn/docs/2022/09/06/01

一、接入

在工程根目录命令行引入依赖库和插件库(必须全部依赖,不能只使用其中一个)。

// 引入代码库依赖
ohpm i @therouter/library   

// 引入插件依赖
npm i @therouter/plugin

第一步:接入编译插件

  1. 打开项目根目录的 hvigor/hvigor-config.json5,检查 dependencies 中是否已经加入了依赖,一般为 "@therouter/plugin": "x.x.x"
  2. 打开工程 所有 模块(hsp、hap、har)的 hvigorfile.ts 文件。
  3. plugins 中加入如下对应的依赖
// 如果是 hap,则类似如下依赖
import { hapPlugin } from "@therouter/plugin";
export default {
  plugins: [hapPlugin()]
}

// 如果是 har,则类似如下依赖
import { harPlugin } from "@therouter/plugin";
export default {
  plugins: [harPlugin()]
}

// 如果是 hsp,则类似如下依赖
import { hspPlugin } from "@therouter/plugin";
export default {
  plugins: [hspPlugin()]
}

第二步:检查依赖是否引入

  1. 打开 工程根目录oh-package.json5 文件。
  2. 检查 dependencies 中,是否已经加入了依赖,一般为:"@therouter/library": "x.x.x"

二、使用

2.1、 初始化

在项目入口的 UIAbilityonCreate() 中加入如下代码:

onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    TheRouter.init(this.context);
}

2.2、 定义页面容器

TheRouter 按照华为推荐方案,基于系统 Navigation 实现,所以必须在页面中定义一个容器项 TheRouterPage,建议创建一个完全新的类作为入口并在 /resources/base/profile/main_pages.json 中配置这个类,复制如下代码:

import { TheRouterPage } from '@therouter/library';

@Entry
@Component
struct Index {
  build() {
    RelativeContainer() {
      TheRouterPage({
        stackId: 'XXXX',  //【必传】可以自定义当前stack的名字,每个stack必须唯一
        root: 'path'  // 【必传】当前应用的首页 path,推荐按照一定格式定义页面path
		// 还有很多可选参数,详情请见文档
      });
    }
    .height('100%')
    .width('100%')
  }
}

2.3、声明路由

给需要跳转的页面加上路由表声明

@Route({path : "http://therouter.com/home"})
export struct HomePage {
	xxx
}

2.4、发起跳转

在需要跳转页面的位置调用如下代码:

TheRouter
	.build("http://therouter.com/home")  
	.withString('k', 'v') // 向落地页传参数(如果没参数,可不调用)
	.with({ hello: 'world' }) // 另一种方式传参
	.navigation()

2.5、获取页面传参与返回给上个页面参数

接收有两种形式:

  • 通过注解自动接收,默认支持 String 和8种基本数据类型,也支持自定义对象的解析
  • 通过代码从路由中获取

使用注解接收对象时,必须调用 TheRouter.inject(this) 。

// 第一种:使用注解自动填充
  // 允许解析成8种基本数据类型或对应封装类
  @Autowired()
  key1 : string = ''

  // 允许自定义传参key,如果不传默认是变量名作为key
  @Autowired('hello')
  key1 : string = ''

  // 使用注解接收对象时,必须调用,建议放在 aboutToApper() 中调用。
  TheRouter.inject(this)
  
  
  
// 第二种:通过代码从路由中获取
  // 可以在任何方法中调用,getCurrentParam() 返回值是个ESObject
  const v = TheRouter.getCurrentParam()['k'];   
  

向上一个页面返回参数,在发起跳转的位置,需要手动声明接收到参数时的处理方法,例如 如下代码:

// 发起跳转:

TheRouter.build(BaseConstant.PAGE_A1).navigation({
    onResult: (routeItem, popInfo) => {
      let param: Map<string, object> = popInfo.result["param"]
      console.log("获取到上个页面返回的数据:" + param["hello"])
    }
  });

// 向上一个页面返回参数   

onBackPress(): boolean | void {
    // 实际业务中,pop() 也可以是点击某个按钮以后执行。
    TheRouter.build().withString("hello", "向上个页面传参数").pop()
    
    // 因为上面调用了 pop(),这里不需要重复调用,所以 return true。
    return true;
}

2.6、生命周期方法

TheRouter 为每个 struct component 内置了 page 具备的全部生命周期方法。意味着,你不再需要像 HMRouter 那样,自己定义全局回调的方式去实现生命周期拦截。

例如,你可以用下面方法做一个“再次点击返回关闭页面”的需求。
另外附带部分生命周期方法展示(未列举全,但应该够用了,其他的生命周期方法可以自行尝试):

⚠️ 注意:要实现生命周期方法,必须调用TheRouter.inject(this),建议放在 aboutToApper() 中调用。

import { Route, TheRouter } from '@therouter/library';
import { BaseConstant } from 'base';

@Route({ path: BaseConstant.PAGE_A2 })
@Component
export struct BusinessAMainPage {
  build() {
    Column() {
      Text('A').margin({ top: 50 })
    }
    .height('100%')
    .width('100%')
  }

  ////////////////////////////////// 如果自定义生命周期方法,必须调用 TheRouter.inject(this) ////////////////////////////////
  
  aboutToAppear(): void {
  // 自定义生命周期必须调用 inject。
    TheRouter.inject(this)
    console.log("aboutToAppear business_aMainPage")
  }

  // 另附一个拦截返回的使用示例
  onBackPress(): boolean | void {
    let time = Date.now();
    if (time - this.lastTime > 1000) {
      this.getUIContext()
        .getPromptAction()
        .showToast({ message: `1s内按两次才关闭页面,当前标题${this.title}`, duration: 1000 });
      this.lastTime = time;
      return true;
    } else {
      return false;
    }
  }

  aboutToDisappear(): void {
    console.log("aboutToDisappear business_aMainPage")
  }

  aboutToRecycle(): void {
    console.log("aboutToRecycle business_aMainPage")
  }

  onWillApplyTheme(theme: Theme): void {
    console.log("onWillApplyTheme business_aMainPage")
  }

  onPageShow(): void {
    console.log("onPageShow business_aMainPage")
  }

  onPageHide(): void {
    console.log("onPageHide business_aMainPage")
  }

  onFormRecycle(): string {
    console.log("onFormRecycle business_aMainPage")
    return ""
  }

  onFormRecover(statusData: string): void {
    console.log("onFormRecover business_aMainPage")
  }

  onNewParam(param: ESObject): void {
    console.log("onNewParam business_aMainPage")
  }
}

附:部分关键生命周期方法图

HarmonyOS lifecycle

2.7、Release 混淆规则

{
  "rules": [
    {
      "name": "keep_all_generated_content",
      "match": {
        "classes": [
          {
            "name": "main/ets/generated/*"
          },
          {
            "name": "main/ets/generated/**/*"
          }
        ]
      },
      "keep": {
        "class": {
          "access": "*",
          "member": {
            "methods": [
              {
                "access": "*"
              }
            ],
            "fields": [
              {
                "access": "*"
              }
            ]
          }
        }
      }
    }
  ]
}

三、问题排查万能公式

在使用过程中,如果发现服务无法调用,或返回为空的时候。通常的排查方式都是以下三步:

  1. 首先观察接入环境。插件是否和依赖库版本一致,插件是否生效了。在我们内部使用的过程中,经常发现,oh-package.json5 中引入的明明是新版本,但是实际编译时使用的还是老版本,猜测应该是构建工具的问题。在TheRouter编译时,日志的第一行就会有当前编译实际使用的版本号,如果和你预期不一致时,需要再执行一下命令行:

     // 引入代码库依赖
     ohpm i @therouter/library   
    
     // 引入插件依赖
     npm i @therouter/plugin
    
  2. 上一步排查完依然无法调用时,就要考虑一下是不是代码使用姿势问题了。比如添加@ServiceProvider的服务提供者必须实现IServiceProvider接口。服务提供者注解参数里面的serviceName是不是和使用方传入的一致。注:如果多次添加重复serviceName,框架会保证安全,在编译时报错。

  3. 如果上两步都不能解决你的问题,那就需要进源码通过断点调试进一步明确问题出在哪里了。首先从TheRouter.init()开始找,看看路由表读取是否正确,再看服务Map里面是否有记录成功,再看调用的时候传入的是否正确,一步一步排查。

四、鸿蒙路由源码调试步骤

4.1、clone 仓库

使用命令行:

git clone git@github.com:HuolalaTech/hll-wp-therouter-harmony.git

4.2、创建 build-profile

在项目根目录中,新建一个名为 build-profile.json5 的文件,文件内加入如下内容。

{
  "app": {
    "signingConfigs": [ 
    // 签名部分需要手动从官网获取自己的开发者信息
     ],
    "products": [
      {
        "name": "default",
        "signingConfig": "xxxxx填上面的签名信息",
        "compatibleSdkVersion": "可根据自己SDK情况填写",
        "runtimeOS": "HarmonyOS",
        "buildOption": {
          "strictMode": {
            "caseSensitiveCheck": true,
            "useNormalizedOHMUrl": true
          }
        }
      }
    ],
    "buildModeSet": [
      {
        "name": "debug",
      },
      {
        "name": "release"
      }
    ]
  },
  "modules": [
    {
      "name": "entry",
      "srcPath": "./entry",
      "targets": [
        {
          "name": "default",
          "applyToProducts": [
            "default"
          ]
        }
      ]
    },
    {
      "name": "therouter",
      "srcPath": "./therouter"
    },
    {
      "name": "business_a",
      "srcPath": "./business_a"
    },
    {
      "name": "business_b",
      "srcPath": "./business_b"
    },
    {
      "name": "base",
      "srcPath": "./base"
    }
  ]
}

4.3、创建 hvigor 构建环境

在工程根目录新建一个名为 hvigor 的文件夹
文件夹内新建一个名为 hvigor-config.json5 的文件,文件内加入如下内容:

{
  "modelVersion": "根据实际情况填写版本号",
  "dependencies": {
    "@therouter/plugin": "根据实际情况填写版本号"  
  },
  "execution": {
    // "analyze": "normal",                     /* Define the build analyze mode. Value: [ "normal" | "advanced" | false ]. Default: "normal" */
    // "daemon": true,                          /* Enable daemon compilation. Value: [ true | false ]. Default: true */
    // "incremental": true,                     /* Enable incremental compilation. Value: [ true | false ]. Default: true */
    // "parallel": true,                        /* Enable parallel compilation. Value: [ true | false ]. Default: true */
    // "typeCheck": false,                      /* Enable typeCheck. Value: [ true | false ]. Default: false */
  },
  "logging": {
    // "level": "info"                          /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */
  },
  "debugging": {
    // "stacktrace": false                      /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */
  },
  "nodeOptions": {
    // "maxOldSpaceSize": 8192                  /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/
    // "exposeGC": true                         /* Enable to trigger garbage collection explicitly. Default: true*/
  }
}