Harmony 动态路由框架:TheRouter 开源
TheRouter 是一个用于移动端APP,包括 Android、iOS、Harmony 三端的模块化、组件化开发的一整套解决方案框架。提供了三端高一致性,对移动端开发者更友好,让开发人员更适应,使用起来也更顺手。在鸿蒙上, TheRouter 基于HMRouter做了深度定制,不仅支持平台化应用实现组件化、跨模块调用、动态化等功能的集成等功能基础上,还提供了编译时安全检查、支持动态路由下发与修改、路由 Path 一对多等高度动态能力。
Github: https://github.com/HuolalaTech/hll-wp-therouter-harmony/
官网:http://therouter.cn/
TheRouter Harmony 核心功能具备如下能力:
- 页面导航跳转能力(Navigator)
- 跨模块依赖注入能力(ServiceProvider)
- 动态化能力(ActionManager)
一、为什么要使用 TheRouter
路由是现如今移动端开发中必不可少的功能,尤其是企业级APP,可以用于将多模块页面跳转的强依赖关系解耦,同时减少跨团队开发的互相依赖问题。
对于大型 APP 开发,基本都会选用模块化(或组件化)方式开发,对于模块间解耦要求更高。 TheRouter 是一整套完全面向模块化开发的解决方案,不仅能支持常规的模块依赖解耦、页面跳转,同时提供了模块化过程中常见问题的解决办法。
1.1 TheRouter 鸿蒙端的三大能力
Navigator:
- 支持
Path与页面多对一关系或一对一关系,可用于解决多端path统一问题 - 页面
Path支持正则表达式声明 - 支持
json格式路由表导出 - 路由表支持为页面添加注释说明
- 支持动态下发
json路由表,降级任意页面为H5 - 支持页面跳转拦截处理
- 支持使用路由跳转到第三方 SDK 中的页面
ServiceProvider:
- 支持跨模块依赖注入
- 支持自定义注入项的创建规则,依赖注入可自定义参数
- 支持注入对象缓存,多次注入,只会 new 一次对象
ActionManager:
- 支持全局回调配置
- 支持多对一链式响应
- 支持优先级响应
- 方法支持返回值与入参
- 支持记录调用路径,解决调试期观察者模式无法追踪
Observable的问题
二、鸿蒙路由方案
根据华为官方文档建议,从API 10开始,推荐使用 NavPathStack 配合 navDestination 属性进行页面路由。所以 TheRouter 也是按照这个方案实现的,如果你的项目还是使用 ohos.router 组件,建议尽早迁移。
详细内容请查看华为官方文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-router
无论哪个平台,路由的本质就是一个 Map,或者说是字典。其中 key 是,页面的 path,value 是路由项。对于 Action、ServiceProvider,也都是一样。
所以在鸿蒙上最核心的重点,是实现在编译期将注解 path 关联到具体的路由项,使其产生一个一对多或一对一的对应关系。其次就是要考虑如何与系统自带的路由表兼容,遵循系统方案而不是完全打造一套,否则可能引起将来的不确定性。
在 TheRouter 中,通过编译期的 hvigor 插件,解析全部的注解关键字,并将获取到的内容保存下来,在应用编译完成后,参照系统的路由表格式,生成一份增量的路由表,聚合到系统的路由表内。正是因为这样,才能做到兼容第三方SDK,正常跳转到第三方页面。既然第三方都能跳转,那么其他的二方、或自己的独立模块自然也就可以正常跳转了。
三、使用 TheRouter 页面跳转
3.1 声明路由项
如果一个页面允许被路由打开,则需要使用注解 @Route 声明路由项,每个页面允许声明多个路由项,也就是一对多的能力,极大降低多端路由统一时的业务影响面。
参数释义
- path: 路由path 【必传】。
建议是一个url,并推荐三端url统一。path内支持使用正则表达式,允许多个path对应同一个Page。 - description: 页面描述【可选】。
会被记录到路由表中,方便后期排查的时候知道每个path或Page是什么业务。 - params: 页面参数【可选】。
自动写入当前页面参数中,允许写在路由表中动态下发修改默认值,或通过路由跳转时代码传入。 - launchMode: 当前页的启动方式【可选】。
启动模式,可选项:'STANDARD'(默认)、'MOVE_TO_TOP_SINGLETON'、'POP_TO_SINGLETON'、'NEW_INSTANCE'。
@Route({ path: BaseConstant.MAIN_PAGE, description: 'Demo首页', params: ["hello", "路由表默认参数"], launchMode:'STANDARD' })
@Component
export struct MainPage {
}
3.2 发起页面跳转
传入的参数可以是 string 和基本数据类型、也可以是ESObject对象。
TheRouter.build("http://therouter.com/home")
.withNumber("key1", 12345678)
.withString("key2", "参数")
.withBoolean("key3", false)
.with({xxx:xxx})
// navigation、replace、pop 均可以额外传入 callback 参数,对当前跳转的个状态回调
.navigation();
// 替换页面(相当于先 pop 再 push)
.replace();
// 关闭当前页
.pop();
3.3 路由表生成规则
如果两条路由的 path 完全相同,则认为是同一条路由,不会考虑参数是否相同。
路由表生成规则:编译期按照如下顺序取并集。
覆盖规则:
根据如下顺序,如果相同,后者可以覆盖前者的路由表规则。
- 编译期解析注解生成路由表
- 首先取
业务模块(har/hap)中的路由表 - 再取 主
hsp module代码中的路由表 - 最后取
resources/base/profile/RouteMap.json文件中声明的路由表。
- 如果编译期没有这个文件,会生成一份默认路由表放在这个目录内(编译完成后如果没有配置保留,会自动删掉);如果有,会将路由表合并。
- 运行时线上动态下发的路由表
- 路由表允许线上动态下发,将覆盖本地路由表,详见 【3.4 动态路由表的设计与使用】
如果编译期没有这个文件,会生成一份默认路由表放在这个目录内(编译完成后如果没有配置保留,会自动删掉);如果有,会将路由表合并,因此,对于没办法修改代码的第三方SDK内部,如果希望通过路由打开,只需要手动在 RouteMap.json 文件中声明,就能通过路由打开了。
3.4 动态路由表的设计与使用
TheRouter 的路由表是动态添加的,项目每次编译后,会在 app 内生成一份当前模块的全量路由表。这个路由表也可以后续通过远程下发的方式使用,例如远端可以针对不同的APP版本,下发不同的路由表达到配置目的。这样如果将来线上某些页面发生Crash,可以通过将这个页面的落地页替换为H5的方式,临时解决这类问题。
有两种推荐的远程下发方式可供使用方选择:
- 将打包系统与配置系统打通,每次新版本打包后自动将所有模块(hsp、har、hap)
resource/rawfile/目录中的路由表文件上传到配置系统,聚合成一个 json 后,下发给对应版本 APP 。优点在于全自动不会出错。 - 配置系统无法打通,线上手动下发需要修改的路由项,因为
TheRouter会自动用最新下发的路由项覆盖包内的路由项。优点在于精确,且流量资源占用小。
动态路由限制 :准确的说,应该是鸿蒙系统的限制。在鸿蒙上,路由表必须是静态的并且在编译期确定下来。TheRouter Harmony 做了一些黑科技处理,允许动态加载一个或多个路由表,但是动态加载的路由页面必须是在编译期就已经存在的,不能凭空新增(类似 Android 的 Activity,在编译后就不能再改或新增注册清单文件了)。
// 与Android逻辑不同,此代码 必须 在页面打开之前,路由初始化之后调用。 建议紧跟 TheRouter.init() 调用
TheRouter.setRouteMapInitTask(task: (map: Map<string, RouteItem>) => void);
/**
* 此处的 map 就是当前应用的路由表全量,
* 当获取到远端路由表以后,把路由表继续传入map中,有重复项可自动覆盖
*/
TheRouter.setRouteMapInitTask(() => {
// 此处为纯业务逻辑,每家公司远端配置方案可能都不一样
const json = Connfig.doHttp("routeMap");
// 只需要将路由json返回给框架即可,不建议在任务中做耗时操作
return json;
});
3.5 拦截器用法
框架内置四种自定义处理器可供业务场景定制,用于在路由跳转过程中,以切面的方式统一修改路由落地页参数信息。
Harmony 路由与 Android 路由的拦截器使用完全一致,可以直接参考【Android 文档 第三部分】。
// 所有拦截器方法均在 TheRouter 类下,可以直接如下方式全局调用
TheRouter.addNavigatorPathFixHandle()
/**
* 应用场景:用于修复客户端上路由 path 错误问题。
* 例如:相对路径转绝对路径,或由于服务端下发的链接无法固定https或http,但客户端代码写死了 https 的 path,就可以用这种方式统一。
* 注:必须在 TheRouter.build() 方法调用前添加处理器,否则处理器前的所有path不会被修改。
*/
static addNavigatorPathFixHandle(handle: NavigatorPathFixHandle);
/**
* 页面替换器
* 应用场景:需要将某些path指定为新链接的时候使用。 也可以用在修复链接的场景,但是与 path 修改器不同的是,修改器通常是为了解决通用性的问题,替换器只在页面跳转时才会生效,更多是用来解决特性问题。
*
* 例如模块化的时候,首页壳模板组件中开发了一个SplashActivity广告组件作为应用的MainActivity,在闪屏广告结束的时候自动跳转业务首页页面。 但是每个业务不同,首页页面的 Path 也不相同,而不希望让每个业务线自己去改这个首页壳模板组件,此时就可以组件中先写占位符https://kymjs.com/splash/to/home,让接入方通过 Path 替换器解决。
* 注:必须在 TheRouter.build().navigation() 方法调用前添加处理器,否则处理器前的所有跳转不会被替换。
*/
static addPathReplaceInterceptor(interceptor: PathReplaceInterceptor);
/**
* 路由替换器
* 应用场景:常用在未登录不能使用的页面上。例如访问用户钱包页面,在钱包页声明的时候,可以在路由表上声明本页面是需要登录的,在路由跳转过程中,如果落地页是需要登录的,则先替换路由到登录页,同时将原落地页信息作为参数传给登录页,登录流程处理完成后可以继续执行之前的路由操作。
*
* 路由替换器的拦截点更靠后,主要用于框架已经从路由表中根据 path 找到路由以后,对找到的路由做操作。
*
* 这种逻辑在所有页面跳转前写不太合适,以前的做法通常是在落地页写逻辑判断用户是否具有权限,但其实在路由层完成更合适。
* 注:必须在 TheRouter.build().navigation() 方法调用前添加处理器,否则处理器前的所有跳转不会被替换。
*/
static addRouterReplaceInterceptor(interceptor: RouterReplaceInterceptor);
/**
* 路由AOP拦截器
* 与前三个处理器不同的点在于,路由的AOP拦截器全局只能有一个。用于实现AOP的能力,在整个TheRouter跳转的过程中,跳转前,目标页是否找到的回调,跳转时,跳转后,都可以做一些自定义的逻辑处理。
*
* 使用场景:场景很多,最常用的是可以拦截一些跳转,例如debug页面在生产环境不打开,或定制startActivity跳转方法。
*/
public setRouterInterceptor(interceptor: (route: RouteItem, callback: (route: RouteItem) => void) => void);
3.6 高级用法
TheRouter同时支持更多页面跳转能力:
- 为第三方库里面的页面添加路由表,达到对某些页面降级替换的目的;
- 跳转过程拦截器(总共四层,可根据实际需求使用);
- 跳转结果回调;
四、跨模块依赖注入 ServiceProvider 的设计
对于模块化开发中跨模块的调用,我们推荐采用 SOA(面向服务架构) 的设计方式,服务调用方与使用方完全隔离,调用模块外的能力不需要关注能力的提供者是谁。
ServiceProvider 的核心设计思想也是这样的,目前服务间的调用协议采用接口的方式。当然,也可以兼容不通过接口下沉而是直接调用的情况。

- 服务提供方负责提供服务,不需要关心调用方是谁会在何时调用自己。
- 服务的使用方只关注服务本身,不需要关心这个服务是谁提供的,只需要只能服务能提供哪些能力即可。
例如上面的图片:拉拉需要使用录音的服务,小货则向外提供一个录音的服务,由TheRouter的ServiceProvider负责撮合。
4.1 服务使用方:拉拉
她无需关心,IRecordService 这个接口服务是谁提供的,他只需要知道自己需要使用这样的一个服务就行了。
注:如果没有提供服务的提供方,TheRouter.get() 可能返回 undefined
TheRouter.get<IRecordService>(BaseConstant.CLASS_SERVICE)?.doRecord()
4.2 服务提供方:小货
服务提供方需要声明一个提供服务的方法,用 @ServiceProvider 注解标记,并需要实现接口 IServiceProvider。
// 类名不限定,任意名字都行
// 所有的 ServiceProvider 必须实现 IServiceProvider 接口
// 多次添加重复serviceName,框架会保证安全,在编译时报错
@ServiceProvider({ serviceName: BaseConstant.CLASS_SERVICE, singleton: true })
export class CustomService implements IRecordService, IServiceProvider {
doRecord(): void {
}
}
@ServiceProvider 参数释义
- serviceName: 服务名 【必传】。
服务的唯一标识。如果重复,在编译期会直接报错。 - singleton: 默认false【可选】。
服务提供方提供出的服务是否为单例。
五、动态化能力 Action 的设计
Action 本质是一个全局的系统回调,主要用于预埋的一系列操作,例如:弹窗、上传日志、清理缓存。
与 Android 系统自带的广播通知类似,你可以在任何地方声明动作与处理方式。并且所有 Action 都是可以被跟踪的,只要你愿意,可以在日志中将所有的动作调用栈输出,以方便调试使用,这样在一定程度上可以解决观察者模式带来的通病:无法追踪 Observable 的问题。
5.1 Action 使用
声明一个 Action:
// action建议遵循一定的格式
const readonly ACTION = "therouter://action/xxx"
// action既可以放在ServiceProvider里面,也可以单独放在任意类中,但不能是top-level函数,这一点与Android不同
// action函数允许有返回值,可以做耗时操作
// 多次添加重复action,每个action的方法都会执行,但最终只会返回优先级最高的方法的返回值
@Action({ action: ACTION })
public test(par: string): string {
return "返回入参:" + par;
}
执行一个 Action:
// action建议遵循一定的格式
const val ACTION = "therouter://action/xxx"
// 如果执行了一个没有被声明的Action,则不会有任何动作
// 这里的"hello"字符串,是根据action定义时有一个入参,所以调用时需要传入这个参数
TheRouter.action<string>(ACTION, "hello").then((str) => {
this.text = str;
})
@Action 参数释义
- action: 事件名 【必传】。
当前函数需要响应的action。如果同一个action有多个函数订阅,会在响应时根据优先级决定先后顺序。 - priority: 优先级(number类型),默认5【可选】。
数字越大,优先级越高。
5.2 客户端动态响应使用场景
如果仅客户端使用,常用的场景可能是:当用户执行某些操作(打开某个页面、H5点击某个按钮、动态页面配置的点击事件)时,将会自动触发,执行预埋的 Action 逻辑。
如果与服务端链路打通,这个能力其实是需要整个公司的配合,比如有一套类似智慧大脑的方案,可以基于客户端过去的一些埋点数据,智能推断出用户下一步要做的事情,然后通过长连接直接向客户端下发指令做某些事情。那么通过客户端预埋的页面跳转、弹窗、清缓存、退出登录等等操作,就可以通过服务端指令进行操作,则就是一套完整的动态化方案。

六、从其他路由迁移至 TheRouter
6.1 迁移工具一键迁移?
TheRouter 在 Android 项目上提供了图形化界面的迁移工具,可以一键从其他路由迁移到TheRouter。
鸿蒙当然也提供了相同的能力,你可以在插件市场搜索,直接安装。安装好以后,点击 IDE 顶部的 Tool 菜单,找到迁移工具。
6.2 与其他路由对比
| 功能 | TheRouter | HMRouter | Navigation |
|---|---|---|---|
| 具备三端高一致性 | ️ | ️ | ️ |
| 注解生成路由表 | ️ | ️ | ️ |
| 路由path支持正则表达式 | ️ | ️ | ️ |
| 指定拦截器 | ️(四大拦截器可根据业务定制) | ️ | ️ |
| 导出路由表 | ️(路由文档支持添加注释描述) | ️ | ️ |
| 支持跨模块调用 | ️ | ️ | ️ |
| 动态修改路由信息 | ️ | ️(未提供功能接口) | ️(限制高,需提前定义,通过if/else修改实现) |
| 远端路由表下发 | ️ | ️ | ️ |
| 多 Path 对应同一页面(低成本实现双端path统一) | ️ | ️ | ️ |
| 支持使用路由打开第三方SDK页面 | ️ | ️ | ️ |
七、总结
TheRouter 并不仅仅是一个小巧灵活的路由库,而是一整套 Android、iOS、Harmony 三端完整的移动端解决方案,对移动端开发者更友好,上手开发适应性更强。使用 TheRouter 能够解决几乎全部的模块化过程中会遇到的问题。
对于现有的路由框架,我们也在最大限度支持平滑迁移。你也可以在 Github issue 中提出需求,我们评估后会尽快支持,也欢迎任何人提供 Pull Requests。
更多问题请加群沟通:

Harmony 动态路由框架:TheRouter 开源的更多相关文章
- WMRouter:美团外卖Android开源路由框架
WMRouter是一款Android路由框架,基于组件化的设计思路,功能灵活,使用也比较简单. WMRouter最初用于解决美团外卖C端App在业务演进过程中的实际问题,之后逐步推广到了美团其他App ...
- 组件化框架设计之阿里巴巴开源路由框架——ARouter原理分析(一)
阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680 背景 当项目的业务越来越复杂,业务线越来越多的时候,就需要按照业 ...
- 第二百六十四节,Tornado框架-基于正则的动态路由映射分页数据获取计算
Tornado框架-基于正则的动态路由映射分页数据获取计算 分页基本显示数据 第一步.设置正则路由映射配置,(r"/index/(?P<page>\d*)", inde ...
- 第二百六十三节,Tornado框架-基于正则的动态路由映射
Tornado框架-基于正则的动态路由映射 1.在路由映射条件里用正则匹配访问路径后缀2.给每一个正则匹配规则(?P<设置名称>)设置一个名称,3.在逻辑处理的get()方法或post() ...
- 比JLRoutes更强大更好用的iOS开源路由框架—FFRouter
目前iOS常用路由框架是JLRouter.HHRouter.MGJRouter. 但是这些路由库都各有不足,首先是JLRouter,用不到的功能繁多,而且基于遍历查找URL,效率低下.HHRouter ...
- Miox带你走进动态路由的世界——51信用卡前端团队
写在前面: 有的时候再做大型项目的时候,确实会被复杂的路由逻辑所烦恼,会经常遇到权限问题,路由跳转回退逻辑问题.这几天在网上看到了51信用卡团队开源了一个Miox,可以有效的解决这些痛点,于是乎我就做 ...
- Android 路由框架ARouter最佳实践
转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/76165252 本文出自[赵彦军的博客] 一:什么是路由? 说简单点就是映射页面跳转 ...
- 从壹开始 [vueAdmin后台] 之三 || 动态路由配置 & 项目快速开发
回顾 今天VS 2019正式发布,实验一波,你安装了么?Blog.Core 预计今天会升级到 Core 3.0 版本. 哈喽大家周三好!本来今天呢要写 Id4 了,但是写到了一半,突然有人问到了关于 ...
- SpringCloud系列——Zuul 动态路由
前言 Zuul 是在Spring Cloud Netflix平台上提供动态路由,监控,弹性,安全等边缘服务的框架,是Netflix基于jvm的路由器和服务器端负载均衡器,相当于是设备和 Netflix ...
- 跟我学SpringCloud | 第十七篇:服务网关Zuul基于Apollo动态路由
目录 SpringCloud系列教程 | 第十七篇:服务网关Zuul基于Apollo动态路由 Apollo概述 Apollo相比于Spring Cloud Config优势 工程实战 示例代码 Spr ...
随机推荐
- Palworld幻兽帕鲁世界参数修改最佳实践(Ubuntu)
本文为您介绍对Palworld游戏世界参数进行修改的最佳实践. 操作场景 本文以Ubuntu操作系统为例,为您介绍通过Palworld专有镜像一键部署Palworld服务器后对游戏世界参数进行修改的具 ...
- 使用CMD命令导出和导入IIS站点配置信息
1.导出导入某个站点 1.1.导出应用程序池和站点 1.1.1.导出某个应用程序池配置 具体导出命令如下所示: %windir%\system32\inetsrv\appcmd list apppoo ...
- Luogu P11553 ROIR 2016 Day 1 奇怪的字符串 题解 [ 绿 ] [ 后缀自动机 ] [ 枚举 ] [ 观察 ]
奇怪的字符串:需要一点观察的 SAM 小清新题. 观察 我们首先观察什么样的字符串才是奇怪的,可以发现,首先类似 AAAAAAA 之类全部相等的字符串是奇怪的. 继续观察,如果字符种类变为两种或者三种 ...
- DeepSeek模型技术动态引行业关注,企业生产系统API迁移需审慎评估
在当今数字化浪潮中,人工智能技术迅猛发展,各类模型如雨后春笋般涌现,而 DeepSeek 模型凭借其独特的优势,在人工智能领域迅速崭露头角,成为备受瞩目的新星. DeepSeek 模型由杭州深度求索人 ...
- RabbitMQ(七)——主题模式
RabbitMQ系列 RabbitMQ(一)--简介 RabbitMQ(二)--模式类型 RabbitMQ(三)--简单模式 RabbitMQ(四)--工作队列模式 RabbitMQ(五)--发布订阅 ...
- 这期没有 AI 开源项目「GitHub 热点速览」
最近 GitHub 上的 AI 开源项目扎堆,几乎到了"刷屏"的程度.所以这次我们换个口味,来看看那些非 AI.有趣的开源项目! Rust 不好学呀!尤其是所有权和生命周期这些概念 ...
- @Scheduled参数及cron表达式解释
@Scheduled支持以下8个参数:1.cron:表达式,指定任务在特定时间执行:2.fixedDelay:表示上一次任务执行完成后多久再次执行,参数类型为long,单位ms:3.fixedDela ...
- 【Spring】Spring的@Autowire注入Bean的规则测试
背景 在项目中使用Spring的Bean,一般都使用默认的Bean的单例,并且结合@Autowire使用. 实在有同一个类型多个实例的情况,也使用@Qualifier或@Resource实现注入. 所 ...
- Deepseek学习随笔(9)--- 清华大学发布Deepseek赋能职场(附网盘链接)
作为一名职场人,在工作中常常面临效率瓶颈:如何快速生成高质量内容?如何高效处理复杂任务?这些问题在接触了<清华大学-DeepSeek赋能职场>这份文档后,得到了全新的解答.这份由清华大学新 ...
- 服务器vps测试脚本大全,新云linux综合工具箱-linux加速脚本 一键硬盘挂载
服务器vps测试脚本大全 一键更换yum脚本 一键优化shh卡顿 一键更换软件源 各种linux加速 BBR原版 bbrplus 魔改plus 锐速 脚本linux加速脚本 一键硬盘挂载 一键cc防御 ...