鸿蒙开发:HMRouter封装教程(四)
在前三篇教程中,我们完成了 HMRouter 的类型封装工具类封装编译插件配置测试页面搭建和拦截器封装。本篇我们将深入探讨HMRouter Service 服务路由的封装和使用。HMRouter Service 是 HMRouter 框架提供的服务路由功能,它允许你通过@HMService和注解将类或方法标记为可调用的服务,实现跨模块、跨页面的服务调用。
📖 前言
在前三篇教程中,我们完成了 HMRouter 的类型封装、工具类封装、编译插件配置、测试页面搭建和拦截器封装。本篇我们将深入探讨 HMRouter Service 服务路由的封装和使用。
HMRouter Service 是 HMRouter 框架提供的服务路由功能,它允许你通过 @HMService 和 @HMServiceProvider 注解将类或方法标记为可调用的服务,实现跨模块、跨页面的服务调用。 
🎯 Service 封装目标
- ✅ 类型统一:封装 Service 相关类型,保持项目代码风格一致
- ✅ 简化使用:提供清晰的 Service 定义和使用示例
- ✅ 功能验证:通过 Service 调用路由跳转,验证 Service 与路由的结合使用
- ✅ 完整测试:创建完整的测试页面,覆盖各种 Service 使用场景
📁 Service 封装结构
entry/src/main/ets/hmRouter/
├── service/ # Service 相关封装目录
│ ├── Index.ets # 统一导出入口
│ ├── HMRouterUtilServiceParam.ets # Service 参数类型别名
│ ├── HMRouterUtilServiceConstants.ets # Service 常量定义
│ └── HMRouterUtilIClassService.ets # Service 接口定义
├── serviceInstance/ # Service 实例目录
│ ├── Index.ets # 统一导出入口
│ └── HMRouterUtilTestService.ets # Service 示例实现
└── model/ # 类型定义目录
├── HMRouterUtilServiceResp.ets # Service 响应类型别名
└── Index.ets # 统一导出(包含 ServiceResp)
🚀 Service 封装步骤
步骤 1:封装 Service 类型定义
1.1 封装 Service 参数类型
文件: service/HMRouterUtilServiceParam.ets
import { HMServiceParam } from '@hadss/hmrouter/src/main/ets/annotation/HMService';
/**
* HMRouter 服务参数类型别名
* 用于项目内部统一使用,定义服务配置参数
*
* 主要属性:
* - serviceName: 服务名称(必填)
* - singleton: 是否单例模式(可选)
*/
export type HMRouterUtilServiceParam = HMServiceParam;
说明:
serviceName:服务的唯一标识,用于调用时定位服务singleton:是否单例模式,默认为false。单例模式下,服务实例会被缓存复用
1.2 封装 Service 响应类型
文件: model/HMRouterUtilServiceResp.ets
import { HMServiceResp } from '@hadss/hmrouter';
/**
* HMRouter 服务响应类型别名
* 用于项目内部统一使用,定义服务执行响应
*/
export type HMRouterUtilServiceResp = HMServiceResp;
HMServiceResp 结构:
class HMServiceResp {
code: number; // 响应码,0 表示成功
msg?: string; // 响应消息
data?: Object; // 响应数据
detail?: Error; // 错误详情(如果有)
}
步骤 2:定义 Service 常量
文件: service/HMRouterUtilServiceConstants.ets
/**
* 服务名称常量
*/
export class HMRouterUtilServiceConstants {
// 类级别服务名称
static readonly CLASS_SERVICE: string = 'HMRouterUtilClassService';
// 方法级别服务名称
static readonly METHOD_CONSOLE: string = 'HMRouterUtilMethodConsole';
static readonly METHOD_WITH_RETURN: string = 'HMRouterUtilMethodWithReturn';
static readonly METHOD_WITH_PARAMS: string = 'HMRouterUtilMethodWithParams';
static readonly METHOD_ASYNC: string = 'HMRouterUtilMethodAsync';
// 路由跳转服务名称
static readonly METHOD_ROUTER_PUSH: string = 'HMRouterUtilMethodRouterPush';
static readonly METHOD_ROUTER_PUSH_WITH_PARAM: string = 'HMRouterUtilMethodRouterPushWithParam';
static readonly METHOD_ROUTER_REPLACE: string = 'HMRouterUtilMethodRouterReplace';
static readonly METHOD_ROUTER_POP: string = 'HMRouterUtilMethodRouterPop';
}
设计思路:
- 使用常量类统一管理服务名称,避免硬编码
- 服务名称建议使用有意义的命名,便于识别和维护
- 分类管理:类级别服务、方法级别服务、路由跳转服务
步骤 3:定义 Service 接口
文件: service/HMRouterUtilIClassService.ets
/**
* 类级别服务接口
* 用于 @HMServiceProvider 装饰的类
*/
export interface HMRouterUtilIClassService {
getData(): string;
processData(data: string): string;
asyncGetData(): Promise<string>;
}
说明:
- 接口定义了类级别服务需要实现的方法
- 支持同步和异步方法
- 使用接口可以保证类型安全,便于后续扩展
步骤 4:实现类级别服务(@HMServiceProvider)
文件: serviceInstance/HMRouterUtilTestService.ets
4.1 类级别服务示例
import { HMServiceProvider } from '@hadss/hmrouter';
import { HMRouterUtilServiceConstants } from '../service/HMRouterUtilServiceConstants';
import { HMRouterUtilIClassService } from '../service/HMRouterUtilIClassService';
import { HMRouterUtil } from '../util/HMRouterUtil';
/**
* 示例1:类级别服务(使用 @HMServiceProvider)
*
* 整个类作为服务提供者,通过 HMRouterUtil.getService() 获取实例
*/
@HMServiceProvider({
serviceName: HMRouterUtilServiceConstants.CLASS_SERVICE,
singleton: true
})
export class HMRouterUtilClassService implements HMRouterUtilIClassService {
/**
* 获取数据
*/
getData(): string {
return 'Data from ClassService';
}
/**
* 处理数据
*/
processData(data: string): string {
return `Processed: ${data}`;
}
/**
* 异步获取数据
*/
async asyncGetData(): Promise<string> {
return new Promise((resolve) => {
setTimeout(() => {
resolve('Async Data from ClassService');
}, 100);
});
}
}
关键点:
- 使用
@HMServiceProvider装饰器标记类为服务提供者 serviceName必须与常量中定义的一致singleton: true表示单例模式,服务实例会被缓存- 类必须实现对应的接口,保证类型安全
4.2 获取类级别服务实例
/**
* 获取类级别服务实例的辅助函数
*/
export function getClassService(): HMRouterUtilIClassService | undefined {
return HMRouterUtil.getService<HMRouterUtilIClassService>(
HMRouterUtilServiceConstants.CLASS_SERVICE
);
}
使用方式:
// 方式1:使用辅助函数
const service = getClassService();
if (service) {
const data = service.getData();
const processed = service.processData('test');
const asyncData = await service.asyncGetData();
}
// 方式2:直接使用 HMRouterUtil.getService
const service = HMRouterUtil.getService<HMRouterUtilIClassService>(
HMRouterUtilServiceConstants.CLASS_SERVICE
);
步骤 5:实现方法级别服务(@HMService)
5.1 方法级别服务示例
import { HMService } from '@hadss/hmrouter';
import { HMRouterUtilServiceConstants } from '../service/HMRouterUtilServiceConstants';
import { HMRouterUtil } from '../util/HMRouterUtil';
/**
* 示例2:方法级别服务(使用 @HMService)
*
* 类中的方法作为独立服务,通过 HMRouterUtil.request() 直接调用
*/
export class HMRouterUtilMethodService {
/**
* 无返回值服务方法
*/
@HMService({ serviceName: HMRouterUtilServiceConstants.METHOD_CONSOLE })
testConsole(): void {
console.log('Calling service: testConsole');
}
/**
* 有返回值服务方法
*/
@HMService({ serviceName: HMRouterUtilServiceConstants.METHOD_WITH_RETURN })
testFunWithReturn(): string {
return 'Calling service: testFunWithReturn';
}
/**
* 带参数服务方法
*/
@HMService({
serviceName: HMRouterUtilServiceConstants.METHOD_WITH_PARAMS,
singleton: true
})
testFunWithParams(str: string, num: number, bool: boolean, obj: TestDataObj): string {
console.log('参数1:', str);
console.log('参数2:', num);
console.log('参数3:', bool);
console.log('参数4:', JSON.stringify(obj));
return obj.paramA || obj.paramB.toString();
}
/**
* 异步服务方法
*/
@HMService({
serviceName: HMRouterUtilServiceConstants.METHOD_ASYNC,
singleton: true
})
async testAsyncFunction(): Promise<string> {
return new Promise((resolve) => {
setTimeout(() => {
resolve('Calling async service: testAsyncFunction');
}, 100);
});
}
}
关键点:
- 使用
@HMService装饰器标记方法为服务 - 每个方法都有独立的
serviceName - 支持同步和异步方法
- 支持带参数的方法调用
5.2 调用方法级别服务
// 无返回值调用
HMRouterUtil.request(HMRouterUtilServiceConstants.METHOD_CONSOLE);
// 有返回值调用
const resp = HMRouterUtil.request(HMRouterUtilServiceConstants.METHOD_WITH_RETURN);
if (resp.code === 0) {
console.log('返回值:', resp.data);
}
// 带参数调用
const obj: TestDataObj = { paramA: 'test', paramB: 100 };
const resp = HMRouterUtil.request(
HMRouterUtilServiceConstants.METHOD_WITH_PARAMS,
'str',
123,
true,
obj
);
// 异步调用
const resp = HMRouterUtil.request(HMRouterUtilServiceConstants.METHOD_ASYNC);
// 使用回调调用
HMRouterUtil.requestWithCallback(
HMRouterUtilServiceConstants.METHOD_WITH_RETURN,
(resp: HMRouterUtilServiceResp) => {
if (resp.code === 0) {
console.log('回调返回值:', resp.data);
}
}
);
步骤 6:通过 Service 调用路由跳转
Service 不仅可以执行业务逻辑,还可以调用路由跳转,实现更灵活的路由控制。
6.1 路由跳转服务方法
export class HMRouterUtilMethodService {
/**
* 路由跳转服务方法(Push)
*/
@HMService({
serviceName: HMRouterUtilServiceConstants.METHOD_ROUTER_PUSH,
singleton: true
})
routerPush(pageUrl: string): string {
HMRouterUtil.push(pageUrl);
return `路由跳转到: ${pageUrl}`;
}
/**
* 路由跳转服务方法(Push with Param)
*/
@HMService({
serviceName: HMRouterUtilServiceConstants.METHOD_ROUTER_PUSH_WITH_PARAM,
singleton: true
})
routerPushWithParam(pageUrl: string, param: Object): string {
HMRouterUtil.push(pageUrl, param);
return `路由跳转到: ${pageUrl}, 参数: ${JSON.stringify(param)}`;
}
/**
* 路由替换服务方法(Replace)
*/
@HMService({
serviceName: HMRouterUtilServiceConstants.METHOD_ROUTER_REPLACE,
singleton: true
})
routerReplace(pageUrl: string, param?: Object): string {
if (param) {
HMRouterUtil.replace(pageUrl, param);
return `路由替换到: ${pageUrl}, 参数: ${JSON.stringify(param)}`;
} else {
HMRouterUtil.replace(pageUrl);
return `路由替换到: ${pageUrl}`;
}
}
/**
* 路由返回服务方法(Pop)
*/
@HMService({
serviceName: HMRouterUtilServiceConstants.METHOD_ROUTER_POP,
singleton: true
})
routerPop(): string {
HMRouterUtil.pop();
return '执行路由返回操作';
}
}
6.2 使用 Service 调用路由跳转
// 通过 Service 调用 Push 跳转
const resp = HMRouterUtil.request(
HMRouterUtilServiceConstants.METHOD_ROUTER_PUSH,
'pages/DetailPage'
);
// 通过 Service 调用 Push(带参数)
const resp = HMRouterUtil.request(
HMRouterUtilServiceConstants.METHOD_ROUTER_PUSH_WITH_PARAM,
'pages/DetailPage',
{ id: 123, name: 'test' }
);
// 通过 Service 调用 Replace
const resp = HMRouterUtil.request(
HMRouterUtilServiceConstants.METHOD_ROUTER_REPLACE,
'pages/ReplaceResultPage'
);
// 通过 Service 调用 Pop
const resp = HMRouterUtil.request(
HMRouterUtilServiceConstants.METHOD_ROUTER_POP
);
应用场景:
- 统一路由管理:将路由跳转逻辑封装在 Service 中,便于统一管理和维护
- 权限控制:在 Service 中添加权限检查,只有通过权限验证才能跳转
- 日志记录:在 Service 中记录路由跳转日志,便于问题排查
- 参数验证:在 Service 中对路由参数进行验证和转换
🧪 Service 测试页面
为了验证 Service 功能的正确性,我们创建了完整的测试页面。
测试页面功能
文件: pages/ServiceTestPage.ets
测试页面包含 13 个测试场景:
类级别服务测试(@HMServiceProvider)
- 测试1:获取类级别服务实例 - 使用辅助函数获取服务实例
- 测试2:直接使用 HMRouterUtil.getService - 直接调用 API 获取服务实例
- 测试3:调用异步方法 - 测试类级别服务的异步方法调用
方法级别服务测试(@HMService)
- 测试4:无返回值服务 - 测试无返回值的服务方法
- 测试5:有返回值服务 - 测试有返回值的服务方法
- 测试6:带参数服务 - 测试带多个参数的服务方法
- 测试7:异步服务 - 测试异步服务方法
- 测试8:使用 requestWithCallback - 测试回调方式调用服务
- 测试9:使用辅助函数 - 测试辅助函数调用服务
路由跳转服务测试(通过 Service 调用路由)
- 测试10:Service 调用 Push 跳转 - 通过 Service 执行路由跳转
- 测试11:Service 调用 Push(带参数) - 通过 Service 执行带参数的路由跳转
- 测试12:Service 调用 Replace 替换 - 通过 Service 执行路由替换
- 测试13:Service 调用 Pop 返回 - 通过 Service 执行路由返回
测试页面代码示例
@HMRouter({ pageUrl: 'pages/ServiceTestPage' })
@Component
export struct ServiceTestPage {
@State logText: string = 'Service 测试日志:\n';
/**
* 测试1:获取类级别服务实例
*/
testGetClassService() {
this.addLog('--- 测试1:获取类级别服务实例 ---');
const service = getClassService();
if (service) {
this.addLog('✓ 成功获取服务实例');
const data = service.getData();
this.addLog(`✓ 调用 getData(): ${data}`);
} else {
this.addLog('✗ 获取服务实例失败');
}
}
/**
* 测试10:通过 Service 调用路由跳转(Push)
*/
testRouterPushByService() {
this.addLog('--- 测试10:通过 Service 调用路由跳转(Push) ---');
const resp = HMRouterUtil.request(
HMRouterUtilServiceConstants.METHOD_ROUTER_PUSH,
'pages/DetailPage'
);
if (resp.code === 0) {
this.addLog('✓ 路由跳转服务调用成功');
this.addLog(`返回值: ${resp.data}`);
this.addLog('页面应该已跳转到 DetailPage');
} else {
this.addLog(`✗ 路由跳转服务调用失败: ${resp.msg}`);
}
}
// ... 其他测试方法
}
💡 使用示例
示例 1:定义数据服务
// 定义服务接口
export interface IDataService {
fetchData(page: number, size: number): Promise<any[]>;
saveData(data: any): Promise<boolean>;
}
// 实现服务提供者
@HMServiceProvider({
serviceName: 'DataService',
singleton: true
})
export class DataServiceImpl implements IDataService {
async fetchData(page: number, size: number): Promise<any[]> {
// 实现数据获取逻辑
return [];
}
async saveData(data: any): Promise<boolean> {
// 实现数据保存逻辑
return true;
}
}
// 使用服务
const dataService = HMRouterUtil.getService<IDataService>('DataService');
if (dataService) {
const data = await dataService.fetchData(1, 20);
console.log('获取的数据:', data);
}
示例 2:定义工具服务
export class UtilService {
@HMService({ serviceName: 'formatDate' })
formatDate(date: Date): string {
return date.toLocaleDateString();
}
@HMService({ serviceName: 'calculateSum' })
calculateSum(...numbers: number[]): number {
return numbers.reduce((sum, num) => sum + num, 0);
}
}
// 使用服务
const resp = HMRouterUtil.request('formatDate', new Date());
const sum = HMRouterUtil.request('calculateSum', 1, 2, 3, 4, 5);
示例 3:通过 Service 实现路由权限控制
export class RouterService {
@HMService({ serviceName: 'navigateWithAuth' })
navigateWithAuth(pageUrl: string, userRole: string): string {
// 权限检查
if (userRole === 'admin') {
HMRouterUtil.push(pageUrl);
return '跳转成功';
} else {
return '权限不足,无法跳转';
}
}
}
// 使用服务
const resp = HMRouterUtil.request('navigateWithAuth', 'pages/AdminPage', 'admin');
⚠️ 注意事项
1. 装饰器必须使用原始名称
HMRouter 编译插件只识别原始的装饰器名称,不能封装:
// ✅ 正确:使用原始装饰器
@HMServiceProvider({ serviceName: 'MyService' })
export class MyService { }
@HMService({ serviceName: 'myMethod' })
myMethod() { }
// ❌ 错误:封装后的装饰器不会被识别
@HMRouterServiceProviderDecorator({ serviceName: 'MyService' })
export class MyService { }
2. 服务名称必须唯一
每个服务的 serviceName 必须在整个应用中唯一,否则会导致服务冲突。
3. 单例模式的使用
// 单例模式:服务实例会被缓存,多次调用返回同一实例
@HMServiceProvider({
serviceName: 'MyService',
singleton: true // 单例模式
})
// 非单例模式:每次调用都会创建新实例
@HMServiceProvider({
serviceName: 'MyService',
singleton: false // 或省略,默认为 false
})
4. 服务响应码检查
const resp = HMRouterUtil.request('myService');
if (resp.code === 0) {
// 服务调用成功
console.log('返回值:', resp.data);
} else {
// 服务调用失败
console.error('错误:', resp.msg);
}
5. 异步服务处理
// 异步服务返回 Promise
@HMService({ serviceName: 'asyncService' })
async asyncMethod(): Promise<string> {
return new Promise((resolve) => {
setTimeout(() => resolve('result'), 1000);
});
}
// 调用异步服务
const resp = HMRouterUtil.request('asyncService');
// resp.data 包含 Promise 的返回值
📊 Service vs 直接调用对比
直接调用路由
// 直接调用,代码分散,难以统一管理
HMRouterUtil.push('pages/DetailPage', { id: 123 });
通过 Service 调用路由
// 通过 Service 调用,可以添加统一逻辑
const resp = HMRouterUtil.request('routerPush', 'pages/DetailPage', { id: 123 });
优势:
- ✅ 统一管理:路由跳转逻辑集中管理
- ✅ 权限控制:可以在 Service 中添加权限检查
- ✅ 日志记录:统一记录路由跳转日志
- ✅ 参数验证:统一验证和转换路由参数
- ✅ 易于测试:Service 方法可以单独测试
🎯 设计原则
|
原则 |
说明 |
|
类型安全 |
使用接口定义服务,保证类型安全 |
|
命名规范 |
服务名称使用常量管理,避免硬编码 |
|
单一职责 |
每个服务专注于特定功能 |
|
易于测试 |
Service 方法可以独立测试 |
|
统一管理 |
通过 Service 统一管理业务逻辑和路由跳转 |
📝 总结
本文介绍了 HMRouter Service 服务路由的封装和使用,包括:
- ✅ 类型封装:封装了
HMRouterUtilServiceParam和HMRouterUtilServiceResp - ✅ 常量管理:使用
HMRouterUtilServiceConstants统一管理服务名称 - ✅ 接口定义:使用接口定义服务,保证类型安全
- ✅ 类级别服务:使用
@HMServiceProvider实现类级别服务 - ✅ 方法级别服务:使用
@HMService实现方法级别服务 - ✅ 路由跳转:通过 Service 调用路由跳转,实现统一管理
- ✅ 完整测试:创建了包含 13 个测试场景的测试页面
通过 Service 封装,我们可以:
- 实现跨模块、跨页面的服务调用
- 统一管理业务逻辑和路由跳转
- 添加权限控制、日志记录等统一逻辑
- 提高代码的可维护性和可测试性
更多推荐



所有评论(0)