本文是系列文章,其他文章见:
鸿蒙@fw/router框架源码解析(一)-router页面管理
鸿蒙@fw/router框架源码解析(二)-Navigation页面管理
鸿蒙@fw/router框架源码解析(三)-Navigation页面容器封装
鸿蒙@fw/router框架源码解析(四)-路由Hvigor插件实现原理
鸿蒙@fw/router框架源码解析(五)-无代码依赖如何实现拦截器逻辑
鸿蒙@fw/router框架源码解析
介绍
@fw/router是在HarmonyOS鸿蒙系统中开发应用所使用的开源模块化路由框架。 该路由框架基于模块化开发思想设计,支持页面路由和服务路由,支持自定义装饰器自动注册,与系统路由相比使用更便捷,功能更丰富。
具体功能介绍见@fw/router:鸿蒙模块化路由框架,助力开发者实现高效模块化开发!
基于模块化的开发需求,本框架支持以下功能:
- 支持页面路由和服务路由;
- 页面路由支持多种模式(router模式,Navigation模式,混合模式);
- router模式支持打开非命名路由页面;
- 页面打开支持多种方式(push/replace),参数传递;关闭页面,返回指定页面,获取返回值,跨页面获取返回值;
- 支持服务路由,可使用路由url调用公共方法,达到跨技术栈调用以及代码解耦的目的;
- 支持页面路由/服务路由通过装饰器自动注册;
- 支持动态导入(在打开路由时才import对应har包),支持自定义动态导入逻辑;
- 支持添加拦截器(打开路由,关闭路由,获取返回值);
- Navigation模式下支持自定义Dialog对话框;
详见gitee传送门
代码解析
RouterManagerForService
概述
RouterManagerForService
主要是为了实现模块化开发中的代码解耦需求。其通过类似页面路由的url参数触发方法调用并获取返回值。
RouterManagerForService
实际上使用的就是中介者设计模式,响应方将自己的功能以url为key实现注册到RouterManagerForService
中,而使用者直接使用url进行调用。我将这种路由称为服务化路由
,即非页面,只提供逻辑服务的路由。
不过,在鸿蒙开发中,还需要考虑两个问题:
1.包引用和包依赖问题;
也就是RouterManager
部分我们讨论的动态导入问题。这个问题已经在RouterManager
解决。
2.响应方如何进行注册;
类似Navigation页面的注册,因为不支持反射,如果我们在EntryAbility.ets中手动注册,那调用注册代码的地方还是要耦合具体的代码,并没有实现完全的解耦。
不过好消息是虽然Navigation页面因为是struct所以不支持自定义装饰器,但普通的class是可以使用的,因此可以通过自定义装饰器来解决注册问题。
所以, 通过动态导入+RouterManagerForService
+自定义装饰器,我们就实现了模块化开发中的包依赖解耦、代码依赖解耦、代码注册解耦。
Route
@Route({ routeName: "testService" })
export class TestServiceRoute extends ServiceRoute {
onAction(pageInstance: ESObject, params: Record<string, ESObject>, callback: SuccessCallback, errorCallback?: ErrorCallback) {
callback({ name: "ZhangSan" })
}
}
这是一个具体的服务路由实现。
@Route
就是用来进行注册的装饰器。
export function Route<T extends { new(...args: any[]): ServiceRoute }>(options: RouteRegisterOptions) {
return function (target: T) {
let item = RouterManagerForService.getInstance().registeMap[options.routeName]
if (item == undefined) {
RouterManagerForService.getInstance().registeRoute(options.routeName, { routeName: options.routeName })
}
let ele = new target()
RouterManagerForService.getInstance().registeMap[options["routeName"]].element ??= ele
};
}
该代码就是自定义装饰器的实现:
<T extends { new(...args: any[]): ServiceRoute }>
用来限定所装饰的类必须是ServiceRoute
的子类;RouterManagerForService.getInstance().registeRoute(options.routeName, { routeName: options.routeName })
先保存一个注册信息对象;let ele = new target()
创建实例;RouterManagerForService.getInstance().registeMap[options["routeName"]].element ??= ele
完成实例注册;
ServiceRoute
export abstract class ServiceRoute {
abstract onAction(pageInstance: ESObject, params: Record<string, ESObject>, callback: SuccessCallback, errorCallback?: ErrorCallback)
}
基类,所有需要注册的实现类都必须实现该抽象基类。
该类只有一个onAction
方法,业务逻辑都在该方法中实现,可以通过params
参数传参。
RouterManagerForService
export class RouterManagerForService implements RouterHandler {
registeMap: Map<string, RouteItem> = new Map();
registeRoute(pageName: string, service: RouteItem) {
this.registeMap[pageName] = service
}
}
使用Map保存服务注册相关实例。
export class RouterManagerForService implements RouterHandler {
open(request: RouterRequestWrapper): Promise<RouterResponse> {
return new Promise((resolve, reject) => {
let routeItem: RouteItem = RouterManagerForService.getInstance().registeMap[request.routeName];
if (routeItem && routeItem.element) {
// 调用服务路由实现
routeItem.element?.onAction(request.rawRequest.pageInstance, request.params, (data) => {
resolve({ code: RouterResponseError.Success.code, msg: RouterResponseError.Success.msg, data: data })
}, (code, message) => {
resolve({ code: code, msg: message })
})
} else {
resolve({
code: RouterResponseError.RequestNotFoundResponsor.code,
msg: RouterResponseError.RequestNotFoundResponsor.msg
})
}
})
}
}
RouterManagerForService
同样实现了RouterHandler
接口,不过对于服务而言,不需要关闭,因此只实现了open
方法。
通过RouterManagerForService.getInstance().registeMap[request.routeName]
获取服务注册对象。
若不存在,则返回RouterResponseError.RequestNotFoundResponsor
错误信息。
若存在,则调用其element实例的onAction
方法。
onAction
方法第一个参数的入参是request的pageInstance
参数,对于普通的服务,并不关心具体是哪个页面在调用;但对于和页面相关的功能,则需要页面对象。
第三第四个参数分别传入了成功回调和失败回调,并使用resolve进行结果处理。
总结
模块化开发中代码解耦的要点在于@Route
装饰器,虽然也有其他方案可替代(比如在Index.ets
中定义固定的registe()
方法,并统一调用);但是装饰器方案相比来说更加方便快捷。