第6.1篇:多模块架构设计------模块拆分与依赖管理
难度 :⭐⭐⭐ 高级
前置知识 :第 1.1 篇 ArkTS 入门
涉及源文件 :
build-profile.json5(根)、oh-package.json5(根)、products/default/oh-package.json5、products/default/module.json5、common/oh-package.json5、features/adaptiveLayout/oh-package.json5、features/responsiveLayout/oh-package.json5、common/Index.ets

一、概述
在"画伴梦工厂"项目中,随着功能不断扩展------自适应布局、响应式布局、语音识别、3D 渲染、服务卡片等------单模块架构逐渐暴露出以下问题:
| 痛点 | 表现 |
|---|---|
| 编译速度慢 | 任何微小改动都需要重新编译整个工程 |
| 耦合严重 | 工具函数、常量、类型定义散落在各个页面目录下,难以复用 |
| 职责边界模糊 | 布局逻辑与业务逻辑混在一起,修改一个功能可能影响其他功能 |
| 不利于团队协作 | 多人同时修改同一模块极易产生代码冲突 |
为了解决这些问题,项目采用了多模块(Multi-Module)架构 。多模块架构是鸿蒙工程中一种成熟的代码组织方式,它将代码按职责拆分为多个独立的模块,每个模块拥有自己的 oh-package.json5 依赖声明和 build-profile.json5 注册项,模块之间通过 file: 协议建立显式的依赖关系。
本文将深入分析"画伴梦工厂"的多模块工程结构,从 build-profile.json5 的模块注册、oh-package.json5 的跨模块依赖声明、module.json5 的模块配置等维度,完整阐述鸿蒙多模块架构的设计思路与最佳实践。
二、四模块架构总览
项目共拆分为 4 个模块,职责划分如下:
drawing-main/
├── common/ # 共享基础库(Shared Library)
│ └── oh-package.json5 # name: @ohos/common
├── features/
│ ├── adaptiveLayout/ # 自适应布局特性(Feature Library)
│ │ └── oh-package.json5 # name: @ohos/adaptivelayout
│ └── responsiveLayout/ # 响应式布局特性(Feature Library)
│ └── oh-package.json5 # name: @ohos/responsivelayout
└── products/
└── default/ # 主应用入口模块(Entry Module)
├── oh-package.json5 # 依赖所有其他模块
├── module.json5 # type: entry
└── src/main/ets/ # 应用代码
2.1 common ------ 共享基础库
common 模块是所有其他模块的公共依赖。它不包含任何业务逻辑,只提供项目范围内的共享基础能力:
| 能力 | 文件 | 说明 |
|---|---|---|
| Logger | common/src/main/ets/utils/Logger.ets |
基于 @kit.PerformanceAnalysisKit 的日志封装,提供 debug/info/warn/error 分级输出 |
| BreakpointSystem | common/src/main/ets/utils/BreakpointSystem.ets |
基于 mediaquery 的断点系统,监测屏幕尺寸变化并同步到 AppStorage |
| BreakPointType<T> | 同上文件的导出类 | 泛型工具,根据当前断点名称返回对应的配置值 |
common 模块通过 Index.ets 作为统一出口导出所有公共 API:
typescript
// common/Index.ets
export { Logger } from './src/main/ets/utils/Logger';
export { BreakpointSystem, BreakPointType } from './src/main/ets/utils/BreakpointSystem';
2.2 adaptiveLayout ------ 自适应布局特性
adaptiveLayout 模块封装了基于 BreakpointSystem 的自适应布局组件和工具函数。它依赖 common 模块获取断点状态,并对外暴露 adaptiveLayout 组件:
typescript
// features/adaptiveLayout/Index.ets
export { adaptiveLayout } from './src/main/ets/pages/AdaptiveLayout';
2.3 responsiveLayout ------ 响应式布局特性
responsiveLayout 模块封装了基于 GridRow/GridCol 的响应式网格布局组件。同样依赖 common 模块,对外暴露 responsiveLayout 组件:
typescript
// features/responsiveLayout/Index.ets
export { responsiveLayout } from './src/main/ets/pages/ResponsiveLayout';
2.4 default ------ 主应用入口
default 模块是 type: "entry" 的主应用模块,包含所有页面、Ability、服务卡片、权限声明等。它是唯一可以打包运行的模块,聚合了所有特性模块的能力。
三、模块依赖关系图
四个模块之间的依赖关系形成一个清晰的有向无环图(DAG):
┌──────────────────────────────────────┐
│ default (entry) │
│ products/default/oh-package.json5 │
└──────┬────────────────────┬───────────┘
│ │
depends on depends on
│ │
▼ ▼
┌────────────────────┐ ┌────────────────────────┐
│ adaptiveLayout │ │ responsiveLayout │
│ (feature library) │ │ (feature library) │
└────────┬───────────┘ └───────────┬────────────┘
│ │
depends on depends on
│ │
▼ ▼
┌──────────────────────────────────────────┐
│ common (shared) │
│ @ohos/common : 1.0.0 │
└──────────────────────────────────────────┘
这个依赖图遵循了单向依赖原则:
common不依赖任何模块adaptiveLayout和responsiveLayout只依赖commondefault依赖所有其他模块
任何两个模块之间都不存在循环依赖,这是多模块架构能够正常编译的基本前提。
四、根 build-profile.json5 ------ 模块注册中心
build-profile.json5 是鸿蒙工程的模块注册配置文件 ,它声明了项目包含的所有模块及其路径。在"画伴梦工厂"中,根 build-profile.json5 的核心结构如下:
json5
{
"app": {
"products": [{
"name": "default",
"targetSdkVersion": "6.0.0(20)",
"compatibleSdkVersion": "6.0.0(20)",
"runtimeOS": "HarmonyOS"
}],
"buildModeSet": [{ "name": "debug" }, { "name": "release" }]
},
"modules": [
{ "name": "default", "srcPath": "./products/default" },
{ "name": "common", "srcPath": "./common" },
{ "name": "adaptiveLayout", "srcPath": "./features/adaptiveLayout" },
{ "name": "responsiveLayout", "srcPath": "./features/responsiveLayout" }
]
}
4.1 modules 数组
每一个 modules 条目定义了一个模块:
| 字段 | 说明 |
|---|---|
name |
模块名称,在项目中必须唯一,用于在构建系统中标识模块 |
srcPath |
模块源码根目录的相对路径(相对于项目根目录) |
注意:模块的 name 与 oh-package.json5 中的 name 字段可以不同 。build-profile.json5 中的 name 是构建系统使用的标识符,而 oh-package.json5 中的 name 是跨模块依赖时引用的包名。
4.2 app.products 与 app.buildModeSet
- products :定义了产品的构建产物配置。
targetSdkVersion和compatibleSdkVersion分别指定目标 SDK 版本和兼容 SDK 版本。runtimeOS指定运行的操作系统。 - buildModeSet :定义可用的构建模式,包括
debug(调试模式,包含调试符号和日志)和release(发布模式,混淆和优化后发布)。
五、oh-package.json5 跨模块依赖声明
5.1 根 oh-package.json5
根目录的 oh-package.json5 声明了项目全局的依赖:
json5
{
"modelVersion": "6.0.0",
"description": "Please describe the basic information.",
"dependencies": {},
"devDependencies": {
"@ohos/hypium": "1.0.24",
"@ohos/hamock": "1.0.0"
}
}
devDependencies 中的 @ohos/hypium 和 @ohos/hamock 是测试框架依赖,仅在开发环境使用,不会打包到最终产物中。
5.2 entry 模块的依赖声明
products/default/oh-package.json5 声明了主模块对三个子模块的依赖:
json5
{
"name": "default",
"version": "1.0.0",
"main": "",
"dependencies": {
"@ohos/adaptivelayout": "file:../../features/adaptiveLayout",
"@ohos/responsivelayout": "file:../../features/responsiveLayout",
"@ohos/common": "file:../../common"
}
}
这里的 file: 协议是鸿蒙工程中跨模块依赖的关键语法。"file:../../features/adaptiveLayout" 表示依赖指向 features/adaptiveLayout 目录下的模块,构建系统会根据该目录下的 oh-package.json5 中的 name 字段解析包名。
5.3 common 模块的依赖声明
json5
{
"name": "@ohos/common",
"version": "1.0.0",
"main": "Index.ets",
"dependencies": {}
}
关键点:
name为@ohos/common,这是其他模块依赖它时使用的包名main指定为Index.ets,这是模块的入口文件,其他模块通过import { ... } from '@ohos/common'导入时,实际上导入的是Index.ets中导出的内容dependencies为空,common是依赖链的最底层
5.4 特性模块的依赖声明
以 adaptiveLayout 为例:
json5
{
"name": "@ohos/adaptivelayout",
"version": "1.0.0",
"main": "Index.ets",
"dependencies": {
"@ohos/common": "file:../../common"
}
}
responsiveLayout 的配置与之类似,同样依赖 @ohos/common。
5.5 file: 协议详解
file: 协议是 HarmonyOS 提供的本地模块引用机制 ,语法为 "file:<相对路径>"。其核心特性包括:
| 特性 | 说明 |
|---|---|
| 路径解析 | 相对路径从当前 oh-package.json5 所在目录开始计算 |
| 自动依赖传递 | 依赖 A 的模块自动获得 A 所依赖的 B 的能力,无需额外声明 |
| 编译时链接 | 构建系统在编译阶段将本地模块的代码链接到最终产物中 |
| 版本无关 | 本地模块的 version 字段不影响依赖解析,始终使用本地源码 |
六、module.json5 模块配置详解
products/default/src/main/module.json5 是 entry 模块的核心配置文件,它定义了模块的类型、支持的设备、页面路由、Ability 组件以及权限声明:
json5
{
"module": {
"name": "default",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "DefaultAbility",
"deviceTypes": ["phone", "tablet", "2in1"],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"requestPermissions": [
{ "name": "ohos.permission.INTERNET" },
{
"name": "ohos.permission.CAMERA",
"reason": "$string:camera_reason",
"usedScene": { "abilities": ["DefaultAbility"], "when": "inuse" }
},
{
"name": "ohos.permission.MICROPHONE",
"reason": "$string:microphone_reason",
"usedScene": { "abilities": ["DefaultAbility"], "when": "inuse" }
},
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC",
"reason": "$string:distributed_data_reason",
"usedScene": { "abilities": ["DefaultAbility"], "when": "inuse" }
}
],
"abilities": [
{
"name": "DefaultAbility",
"srcEntry": "./ets/defaultability/DefaultAbility.ets",
"description": "$string:ability_desc",
"icon": "$media:layered_image",
"label": "$string:ability_label",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"skills": [{
"entities": ["entity.system.home"],
"actions": ["ohos.want.action.home"]
}]
}
],
"extensionAbilities": [
{
"name": "CreationFormAbility",
"srcEntry": "./ets/creationformability/CreationFormAbility.ets",
"type": "form",
"metadata": [{ "name": "ohos.extension.form", "resource": "$profile:form_config" }]
},
{
"name": "DefaultBackupAbility",
"srcEntry": "./ets/defaultbackupability/DefaultBackupAbility.ets",
"type": "backup",
"exported": false,
"metadata": [{ "name": "ohos.extension.backup", "resource": "$profile:backup_config" }]
}
]
}
}
6.1 关键字段解读
| 字段 | 值 | 含义 |
|---|---|---|
name |
"default" |
模块名称,与 build-profile.json5 中的 name 对应 |
type |
"entry" |
模块类型,entry 表示可独立运行的应用入口模块 |
deviceTypes |
["phone", "tablet", "2in1"] |
目标设备类型,支持手机、平板和二合一设备 |
pages |
"$profile:main_pages" |
页面路由配置,指向 resources/base/profile/main_pages.json |
mainElement |
"DefaultAbility" |
应用入口 Ability |
6.2 requestPermissions ------ 安全权限声明
项目声明了四种权限:
- INTERNET:网络访问权限,用于图片上传和 API 调用
- CAMERA :相机权限,用于拍照采集画作,仅在
inuse(前台使用时)申请 - MICROPHONE:麦克风权限,用于语音识别功能
- DISTRIBUTED_DATASYNC:分布式数据同步权限,用于跨设备数据共享
每个危险权限都附带了 reason 字符串资源,在权限申请弹窗中向用户说明使用目的,这符合鸿蒙的安全最佳实践。
6.3 extensionAbilities ------ 扩展能力
- CreationFormAbility (type:
form):服务卡片 Ability,提供桌面小组件功能 - DefaultBackupAbility (type:
backup):数据备份 Ability,支持应用数据的云备份与恢复
七、模块类型详解:entry / feature / shared
在鸿蒙多模块架构中,模块按职责分为三种类型:
7.1 entry 模块
json5
{ "type": "entry" }
- 用途 :应用的主入口模块,包含 Ability、页面路由、权限声明等应用级配置
- 特性 :
- 只能有一个 entry 模块(在
build-profile.json5的products中定义) - 必须包含
module.json5且type为"entry" - 可以引用所有 feature 和 shared 模块
- 最终生成可安装的 HAP 包
- 只能有一个 entry 模块(在
在项目中,products/default 就是 entry 模块,它负责聚合所有特性模块并提供最终的 UI 页面和应用生命周期管理。
7.2 feature 模块
feature 模块没有独立的 module.json5,而是通过 oh-package.json5 声明自身并提供导出。
- 用途:封装独立的业务特性或功能,如自适应布局、响应式布局
- 特性 :
- 可以有多个 feature 模块
- 可以依赖 shared 模块和其他 feature 模块
- 不包含 Ability 或页面路由配置
- 通过
Index.ets对外暴露 API
项目中 features/adaptiveLayout 和 features/responsiveLayout 都是 feature 模块,两者职责清晰、互不依赖。
7.3 shared 模块
- 用途:提供跨模块共享的基础能力,如工具类、常量、日志等
- 特性 :
- 可以有多个 shared 模块
- 不应依赖任何 feature 模块(保持最底层位置)
- 通过
Index.ets作为模块入口统一导出
项目中的 common 模块是典型的 shared 模块,它不依赖任何其他业务模块,其他所有模块都可以引用它。
八、Index.ets ------ 模块入口约定
在鸿蒙多模块架构中,每个通过 oh-package.json5 导出的模块都需要指定一个入口文件 (main 字段)。common 模块的入口文件如下:
typescript
// common/Index.ets
export { Logger } from './src/main/ets/utils/Logger';
export { BreakpointSystem, BreakPointType } from './src/main/ets/utils/BreakpointSystem';
这种做法形成了清晰的门面模式(Facade Pattern):
- 对外隐藏内部实现细节 :其他模块只需关心
Index.ets中导出了什么,无需了解utils/目录的组织结构 - 统一引用入口 :
import { Logger } from '@ohos/common'即可获取日志能力 - 控制可访问性 :
Index.ets中只导出需要暴露的 API,内部类和常量不对外可见
九、common 模块核心能力剖析
9.1 Logger ------ 统一日志服务
typescript
import { hilog } from '@kit.PerformanceAnalysisKit';
export class Logger {
private readonly prefix: string = 'testTag';
private readonly domain: number = 0xFF00;
private readonly format: string = '%{public}s, %{public}s';
public debug(...args: string[]): void {
hilog.debug(this.domain, this.prefix, this.format, args);
}
public info(...args: string[]): void {
hilog.info(this.domain, this.prefix, this.format, args);
}
public warn(...args: string[]): void {
hilog.warn(this.domain, this.prefix, this.format, args);
}
public error(...args: string[]): void {
hilog.error(this.domain, this.prefix, this.format, args);
}
}
Logger 封装了鸿蒙的 hilog 日志接口,提供了 debug、info、warn、error 四个等级的输出方法。所有模块统一使用这个 Logger,避免了日志输出格式不一致的问题。
9.2 BreakpointSystem ------ 媒体查询断点系统
BreakpointSystem 是自适应布局的核心基础设施。它通过 mediaquery.MediaQueryListener 监听屏幕宽度变化,并在断点切换时更新 AppStorage 中的全局状态:
typescript
export class BreakpointSystem {
private readonly breakpoints: Breakpoint[] = [
{ name: 'sm', size: 320 },
{ name: 'md', size: 600 },
{ name: 'lg', size: 840 },
{ name: 'xl', size: 1080 }
];
public register(uiContext: UIContext): void {
// 为每个断点范围注册媒体查询监听器
this.breakpoints.forEach((breakpoint, index) => {
let condition = '';
if (index === this.breakpoints.length - 1) {
condition = `(${breakpoint.size}vp<=width)`;
} else {
condition = `(${breakpoint.size}vp<=width<${this.breakpoints[index + 1].size}vp)`;
}
// ... 注册监听
});
}
}
common 模块还将 BreakPointType<T> 泛型工具类一并导出,使得特性模块可以根据当前断点名称获取对应的配置值(如间距、字号等),实现真正的响应式布局。
9.3 为什么不把这些放在 entry 模块?
将 Logger 和 BreakpointSystem 放在 common 模块而非 default 模块,是基于以下架构考量:
-
避免循环依赖 :如果 Logger 放在
default模块中,那么adaptiveLayout要使用 Logger 就必须依赖default,而default又依赖adaptiveLayout,形成循环依赖,违反 DAG 原则。 -
模块复用性 :
common模块可以独立复用。如果将来项目需要增加一个wearable产品线,只需在build-profile.json5中注册新的 entry 模块,并复用已有的common和features/*模块。 -
编译效率 :
common模块的内容不常变更,编译一次后可以被缓存,后续重新编译其他模块时无需重新编译common,显著提升增量编译速度。
十、架构决策原理与最佳实践
10.1 为什么选择多模块架构?
单模块架构(不采用) 多模块架构(采用)
┌─────────────────────┐ ┌─────────────────────┐
│ 所有代码混在一起 │ │ clear: entry │
│ 大到难以维护 │ │ │ │
│ 编译需要 5 分钟 │ │ ├─ feature: 布局 │
│ 改一行全局重编 │ │ ├─ feature: 语音 │
│ 新人上手困难 │ │ └─ shared: common │
└─────────────────────┘ │ 编译只需 1-2 分钟 │
│ 模块职责清晰 │
│ 可独立开发和测试 │
└─────────────────────┘
10.2 模块拆分原则
在实际项目中,建议遵循以下原则进行模块拆分:
| 原则 | 说明 | 项目中的体现 |
|---|---|---|
| 单一职责 | 每个模块只做一件事 | common 只做共享基础库,adaptiveLayout 只做自适应布局 |
| 无环依赖 | 依赖图必须为 DAG | common ← adaptiveLayout ← default |
| 接口隔离 | 通过 Index.ets 控制导出范围 |
只导出需要公开的 API |
| 稳定方向依赖 | 更稳定的模块被更低层依赖 | common 最稳定,位于底层 |
| 共同复用 | 一起变化的类放在同一模块 | 布局相关组件集中在各自 feature 模块 |
10.3 常见的架构陷阱
陷阱一:过度拆分
模块的数量不是越多越好。通常 3~8 个模块对于中型项目是比较合理的区间。如果模块粒度过细(每个工具类一个模块),反而会增加依赖管理的复杂度。
陷阱二:循环依赖
鸿蒙构建系统不支持循环依赖。如果 A 依赖 B、B 依赖 C、C 又依赖 A,编译会直接报错。解决方法是:将循环双方的共同依赖抽离到一个新的 shared 模块中。
陷阱三:跨模块直接引用内部文件
typescript
// ❌ 错误做法------跨模块引用内部路径
import { Logger } from '../../common/src/main/ets/utils/Logger';
typescript
// ✅ 正确做法------通过模块名引用
import { Logger } from '@ohos/common';
直接引用内部路径会绕过模块的 Index.ets 入口,导致模块的封装性被破坏。如果以后重构内部文件结构,所有直接引用的地方都需要修改。
10.4 构建性能优化
多模块架构带来的一个核心收益是增量编译 。在鸿蒙 DevEco Studio 中,当修改 features/adaptiveLayout 的代码时:
- 构建系统检测到
adaptiveLayout的源文件发生变更 - 仅重新编译
adaptiveLayout模块及其直接依赖者(如default) common和responsiveLayout模块无需重新编译
对于大型项目,这种增量编译可以将构建时间从数分钟降低到数十秒。
总结
本文通过"画伴梦工厂"的实际项目代码,完整分析了鸿蒙多模块架构的设计与实现:
| 知识点 | 对应文件/配置 | 核心要点 |
|---|---|---|
| 模块注册 | build-profile.json5 |
modules 数组注册所有模块,products 定义构建产物 |
| 依赖声明 | oh-package.json5 |
file: 协议声明本地模块依赖,name/main 定义模块入口 |
| 模块配置 | module.json5 |
type: entry 定义入口模块,deviceTypes 指定支持设备 |
| 模块类型 | 三者搭配使用 | entry(主应用)、feature(特性库)、shared(共享库) |
| 模块入口 | Index.ets |
门面模式统一导出,隐藏内部实现 |
| 共享能力 | common 模块 |
提供 Logger、BreakpointSystem 等跨模块基础设施 |
| 依赖图 | 整体结构 | DAG 单向依赖,无循环引用 |
| 最佳实践 | 架构原则 | 单一职责、接口隔离、稳定方向依赖 |
多模块架构是鸿蒙工程从"能跑就行"迈向"可维护、可扩展"的关键一步。它为项目的持续迭代奠定了坚实的工程基础,也是团队协作开发的必备架构模式。
下一篇: 第 6.2 篇将探讨鸿蒙工程的构建配置进阶------多产物构建、签名管理与 CI/CD 流水线集成,敬请期待。