HarmonyOS掌上记账APP开发实践第1篇:HarmonyOS 多模块 HAR 架构最佳实践 — MoneyTrack 多模块拆分之道
HarmonyOS 多模块 HAR 架构最佳实践 — MoneyTrack 多模块拆分之道

概述
在大型鸿蒙应用开发中,随着业务功能的不断迭代,工程规模会迅速膨胀。如果将所有代码堆砌在单一的 entry 模块中,将会面临一系列棘手问题:编译时间随代码量线性增长、模块间耦合严重导致"改一处动全身"、团队协作时频繁出现代码冲突、业务复用困难等。这些问题在项目达到数十万行代码规模后会急剧恶化,严重拖累开发效率。
MoneyTrack 作为一款功能完备的记账应用,涵盖了账单管理、资产管理、数据报表、家庭账本、会员订阅、消息提醒等多个业务领域,代码量庞大且逻辑复杂。为了应对上述挑战,项目采用多模块 HAR(Harmony Archive)架构,将 21 个功能模块按四层架构精心组织,实现了高度的内聚与低耦合。本文深入剖析该项目的模块划分策略、依赖管理方式以及跨模块引用设计,为构建可扩展的鸿蒙应用提供实战参考。
核心知识点
1. 四层分层架构设计
MoneyTrack 将 21 个 HAR 模块划分为四个层次,层与层之间遵循严格的单向依赖关系:
- 通用层(commons):包含
commonlib(工具库、路由、EventBus、断点适配)和lib_network(网络请求封装与 Mock 适配器)。该层不依赖任何业务模块,为全项目提供基础设施能力。 - 组件层(components):包含
bill_base、asset_base、bill_card、feedback等 14 个模块。每个模块封装独立的业务组件或数据模型,可被多个特性层模块复用。例如bill_base提供账单相关的枚举、类型定义和数据模型,bill_card提供账单卡片 UI 组件。 - 特性层(features):包含
home、statistics、assets、mine四个模块,分别对应应用的四个 Tab 页面。每个特性模块按业务领域聚合页面、ViewModel 和组件,是业务逻辑的核心载体。 - 入口层(entry):位于
products/entry,作为应用唯一入口,组装各模块的能力,配置 Ability 和元数据。
2. 模块间依赖管理
在根目录 build-profile.json5 中,21 个模块被逐一注册。以下展示完整的模块注册配置:
{
"app": {
"products": [
{
"name": "default",
"signingConfig": "default",
"targetSdkVersion": "6.0.2(22)",
"compatibleSdkVersion": "5.0.5(17)",
"runtimeOS": "HarmonyOS",
"buildOption": {
"strictMode": {
"caseSensitiveCheck": true,
"useNormalizedOHMUrl": true
}
}
}
]
},
"modules": [
{ "name": "entry", "srcPath": "./products/entry" },
{ "name": "commonlib", "srcPath": "./commons/commonlib" },
{ "name": "lib_network", "srcPath": "./commons/lib_network" },
{ "name": "home", "srcPath": "./features/home" },
{ "name": "statistics", "srcPath": "./features/statistics" },
{ "name": "assets", "srcPath": "./features/assets" },
{ "name": "mine", "srcPath": "./features/mine" },
{ "name": "bill_base", "srcPath": "./components/bill_base" },
{ "name": "bill_card", "srcPath": "./components/bill_card" },
{ "name": "bill_chart", "srcPath": "./components/bill_chart" },
{ "name": "bill_manage", "srcPath": "./components/bill_manage" },
{ "name": "bill_data_processing", "srcPath": "./components/bill_data_processing" },
{ "name": "asset_base", "srcPath": "./components/asset_base" },
{ "name": "asset_card", "srcPath": "./components/asset_card" },
{ "name": "asset_manage", "srcPath": "./components/asset_manage" },
{ "name": "aggregated_login", "srcPath": "./components/aggregated_login" },
{ "name": "app_setting", "srcPath": "./components/app_setting" },
{ "name": "collect_personal_info", "srcPath": "./components/collect_personal_info" },
{ "name": "feedback", "srcPath": "./components/feedback" },
{ "name": "membership", "srcPath": "./components/membership" },
{ "name": "module_ui_base", "srcPath": "./components/module_ui_base" }
]
}
每个模块通过 oh-package.json5 声明其依赖。以 home 特性模块为例,它依赖多个组件层模块和通用层模块,形成清晰的单向依赖链:
// features/home/oh-package.json5
{
"name": "home",
"version": "1.0.0",
"main": "Index.ets",
"dependencies": {
"commonlib": "file:../../commons/commonlib",
"bill_manage": "file:../../components/bill_manage",
"bill_data_processing": "file:../../components/bill_data_processing",
"bill_base": "file:../../components/bill_base",
"bill_card": "file:../../components/bill_card",
"lib_network": "file:../../commons/lib_network",
"dayjs": "1.11.13"
}
}
再来看 entry 模块的依赖声明——它作为应用入口,依赖了所有特性模块和部分组件模块:
// products/entry/oh-package.json5
{
"name": "entry",
"version": "1.0.0",
"main": "",
"dependencies": {
"commonlib": "file:../../commons/commonlib",
"lib_network": "file:../../commons/lib_network",
"home": "file:../../features/home",
"statistics": "file:../../features/statistics",
"assets": "file:../../features/assets",
"mine": "file:../../features/mine",
"bill_data_processing": "file:../../components/bill_data_processing",
"aggregated_login": "file:../../components/aggregated_login",
"app_setting": "file:../../components/app_setting",
"feedback": "file:../../components/feedback",
"membership": "file:../../components/membership",
"collect_personal_info": "file:../../components/collect_personal_info",
"dayjs": "1.11.13"
}
}
3. 跨模块引用路径设计
在鸿蒙 HAR 包中,通过 oh-package.json5 中的 "main": "Index.ets" 统一导出入口,外部模块只需引用包名而非深层路径。以 commonlib 为例,其 Index.ets 统一导出了所有工具类和组件:
// commons/commonlib/Index.ets
export { Logger } from './src/main/ets/utils/logger/Index';
export { WindowUtil } from './src/main/ets/utils/window/Index';
export { FrameworkUtil, TabIndexMap } from './src/main/ets/utils/framework/Index';
export { EventBus, EventKey } from './src/main/ets/utils/eventbus/Index';
export { contextUtil } from './src/main/ets/utils/ContextUtil';
export { ShareUtil } from './src/main/ets/utils/ShareUtil';
export * from './src/main/ets/utils/userinfo/Index';
export * from './src/main/ets/utils/router/Index';
export {
BreakpointTypeEnum, BreakpointType, BreakpointModel,
BreakpointStorage, BreakpointSystem,
} from './src/main/ets/utils/BreakpointSystem';
export { CommonConstants } from './src/main/ets/constants/CommonConstants';
export { CommonHeader } from './src/main/ets/components/CommonHeader';
export { CommonMonthPicker } from './src/main/ets/components/CommonMonthPicker';
export { CommonDayPicker } from './src/main/ets/components/CommonDayPicker';
export { CommonButton, CustomButtonType } from './src/main/ets/components/CommonButton';
export { ContainerRow } from './src/main/ets/components/ContainerRow';
export { ContainerColumn } from './src/main/ets/components/ContainerColumn';
export { CommonDivider } from './src/main/ets/components/CommonDivider';
export { ConfirmParam } from './src/main/ets/dialogs/CommonConfirmDialog';
export { ReminderUtil } from './src/main/ets/utils/ReminderUtil';
export { ReminderSettingVM } from './src/main/ets/utils/ReminderSettingVM';
其他模块通过包名直接引用:
// features/home/src/main/ets/viewmodels/HomeVM.ets
import {
ConfirmParam, EventBus, EventKey, RouterModule, WindowUtil
} from 'commonlib';
import { AccountingDB, BillProcessingModel } from 'bill_data_processing';
import { BalanceChangeType, BalanceResourceModel, ResourceUtil } from 'bill_base';
这种设计带来了几个好处:引用路径简短且统一、重构时只需修改 Index.ets 的导出、对外隐藏内部实现细节。
4. 面包屑导航与路由设计
MoneyTrack 使用基于 Navigation 组件的路由体系,通过 commonlib 中的 RouterModule 统一管理页面栈:
// commons/commonlib/src/main/ets/utils/router/Router.ets
export class RouterModule {
private static _stack: NavPathStack = new NavPathStack();
public static getStack() {
return RouterModule._stack;
}
public static push(info: NavRouterInfo, animated?: boolean) {
RouterModule._stack.pushPathByName(info.url, info.param, info.onPop, animated);
}
public static replace(info: NavRouterInfo) {
RouterModule._stack.replacePathByName(info.url, info.param);
}
public static pop(result?: Object, animated?: boolean) {
RouterModule._stack.pop(result, animated);
}
public static clear(animated?: boolean) {
// 清除栈中所有页面
}
}
每个模块在 resources/base/profile/ 目录下维护自己的 router_map.json,实现了路由配置的模块自治。
项目案例
根目录 build-profile.json5(d:\HarmonyOS\WorkSpace\MoneyTrack1.0.3\build-profile.json5)完整展示了 21 个模块的注册配置。以 bill_data_processing 和 commonlib 为例,前者封装数据库处理逻辑(包含关系型数据库 AccountingDB 和 BaseDB),后者提供路由、EventBus、断点系统等通用能力——这种分层使得替换底层实现时无需修改上层代码。
实际的跨模块调用示例——在 HomeVM 中通过 EventBus 监听全局刷新事件:
public async initData() {
await this.dataProcessing.accountProcessing.init();
await this.refreshBill();
EventBus.instance.on(EventKey.GLOBAL_REFRESH_EVENT, () => {
this.refreshBill();
});
}
EventBus 的底层实现基于 AbilityKit 的 EventHub 能力,代码极其简洁:
export class EventBus {
private _eventHub = (contextUtil.getContext() as common.UIAbilityContext).eventHub;
private static _instance: EventBus;
public static get instance(): EventBus { /* 单例实现 */ }
public emit(eventKey: EventKey | string, ...args: Object[]) {
this._eventHub.emit(eventKey, ...args);
}
public on(eventKey: string, callback: Function) {
this._eventHub.on(eventKey, callback);
}
public off(eventKey: string, callback?: Function) {
this._eventHub.off(eventKey, callback);
}
}
最佳实践与注意事项
1. 坚持单向依赖原则
依赖方向必须严格遵循:通用层 ← 组件层 ← 特性层 ← 入口层。绝不允许下层反向依赖上层。例如 commonlib 不依赖任何业务模块,bill_base 只能依赖 commonlib 而不能依赖 home。这种约束通过 oh-package.json5 的依赖声明即可强制执行。
2. 避免循环依赖
循环依赖是模块化架构中最常见也最隐蔽的问题。例如 模块A 依赖 模块B,而 模块B 又直接或间接依赖 模块A,会导致编译失败或运行时异常。在实践中,应将可能产生双向依赖的共用类型抽离到更底层的模块(如 bill_base 或 commonlib)中。在 MoneyTrack 中,bill_base 承担了"基础类型仓库"的角色,所有账单相关的枚举、接口和数据模型都在此定义。
3. 接口隔离与最小化导出
每个 HAR 模块应通过 Index.ets 精确控制对外暴露的接口,避免将内部实现细节暴露给外部。遵循"最少知识原则"——只导出外部确实需要引用的类、函数和类型。同时,对于跨模块共享的 ViewModel 和 Model,使用 @ObservedV2 和 @Trace 装饰器确保响应式状态在模块间正确传递。
4. 模块粒度控制
模块拆分不宜过细也不宜过粗。过细会导致大量小模块管理成本上升,过粗则失去了模块化的意义。实践中,可按"业务聚合度"为标准:一组高内聚的、可能被多个上层复用的功能,应独立为一个 HAR 模块。MoneyTrack 中 21 个模块的粒度就是经过多次迭代后沉淀下来的最优解。
5. 版本管理一致化
所有 HAR 模块应保持版本号一致(推荐跟随主应用版本),避免出现"模块版本矩阵"导致的依赖冲突。在根目录统一管理依赖版本,子模块通过 file: 协议引用本地模块时不需要额外关注版本匹配问题。
总结
多模块 HAR 架构是鸿蒙大型应用工程的基石。MoneyTrack 通过四层架构的精心设计,实现了 21 个模块的高内聚低耦合。通用层提供基础设施,组件层封装可复用的业务组件和数据模型,特性层聚合领域页面,入口层完成最终组装。配合单向依赖原则、接口隔离和统一的包导出设计,这一架构支撑了项目的快速迭代和团队高效协作。在实际项目中,应根据业务复杂度灵活调整模块粒度,始终将"可维护性"和"可扩展性"作为架构设计的首要目标。
参考文档
- oh-package.json5 配置指南
- HAR 包构建与引用规范
- HarmonyOS 多模块工程最佳实践
- 项目源文件:
d:\HarmonyOS\WorkSpace\MoneyTrack1.0.3\build-profile.json5 - 各模块 oh-package.json5 示例:
features/home/oh-package.json5、products/entry/oh-package.json5
更多推荐

所有评论(0)