鸿蒙百篇系列第 71 篇 · 专家
第 10 篇讲过架构设计的模块化思路,第 34 篇讲过组件化拆分。到第 70 篇为止,你已经掌握了从渲染管线到系统服务的底层知识。从这篇开始,我们进入"专家架构"阶段——把这些底层能力用工程化的方式组织起来,解决大型鸿蒙应用的分层问题。
一个鸿蒙应用从 Demo 到正式产品,最大的转折点不是"功能做完了",而是"代码管不住了"。三五十个模块互相依赖,改一个接口牵连十处编译错误,新来的同事打开工程看半小时才找到入口在哪。这时候你需要的不是某个 API 技巧,而是一套分层架构。
一、为什么要分层
不分层的应用长什么样?一个典型反例:
src/main/ets/
├── pages/
│ ├── HomePage.ets // 首页里直接写了 HTTP 请求
│ ├── DetailPage.ets // 详情页里直接操作数据库
│ └── MinePage.ets // 个人中心里直接写 SharedPreferences
├── model/
│ └── User.ets // 数据模型和 UI 组件混在一起
└── common/
└── utils.ets // 所有工具函数堆在一个文件
这种结构在功能数到 10 个页面以内还能跑,超过就开始出问题:
首页想换网络库,发现 HTTP 调用散落在 8 个页面里
详情页改了数据结构,个人中心的 UI 也崩了
测试网络请求要启动整个应用
分层的本质是把"关注点"隔离开。UI 层不关心数据从哪来,数据层不关心 UI 怎么显示。鸿蒙的官方推荐三层架构:产品定制层、基础特性层、公共能力层。但在大型应用实践中,需要更细的划分。
二、四层架构设计
在华为官方三层架构基础上,结合实际工程经验,大型鸿蒙应用建议采用四层架构:
┌─────────────────────────────────────────┐
│ 产品定制层 (products) │ ← entry 模块
│ 应用入口、设备适配、页面路由编排 │
├─────────────────────────────────────────┤
│ 基础特性层 (features) │ ← feature HAP / HSP
│ 登录、支付、消息、搜索等业务模块 │
├─────────────────────────────────────────┤
│ 能力层 (abilities) │ ← HSP 动态共享包
│ 网络通信、数据存储、日志埋点、安全 │
├─────────────────────────────────────────┤
│ 公共层 (common) │ ← HAR 静态共享包
│ UI 组件库、工具类、类型定义、常量 │
└─────────────────────────────────────────┘
各层之间只能向下依赖,不能反向引用。
层级职责
变更频率从上到下递减。公共层的接口定义一旦稳定,几乎不会改动。产品定制层每个版本都在变。这个设计让底层变更的影响范围可控。
三、模块形态选型
鸿蒙提供三种模块形态:HAP、HAR、HSP。选型直接决定架构能不能跑通。
关键决策点:
如果一个 HAR 被 3 个以上 HAP 引用,改成 HSP。否则同一份代码拷贝 3 份,包体积白白增大。
如果一个 HSP 只被 1 个 HAP 引用,改成 HAR。HSP 运行时加载有额外开销,单引用场景不划算。
公共层用 HAR。类型定义和工具函数没有运行时状态,编译时集成效率最高。
四、目录结构落地
以一个电商应用为例,四层架构的工程目录如下:
myapp/
├── entry/ # 产品定制层 (HAP)
│ └── src/main/ets/
│ ├── entryability/
│ │ └── EntryAbility.ets # 应用入口
│ └── pages/
│ └── MainPage.ets # 首页(编排各 feature 的页面)
│
├── features/ # 基础特性层
│ ├── feature_home/ # 首页模块 (HSP)
│ │ └── src/main/ets/
│ │ └── pages/HomePage.ets
│ ├── feature_cart/ # 购物车模块 (HSP)
│ │ └── src/main/ets/
│ │ └── pages/CartPage.ets
│ └── feature_mine/ # 个人中心模块 (HSP)
│ └── src/main/ets/
│ └── pages/MinePage.ets
│
├── abilities/ # 能力层
│ ├── ability_network/ # 网络通信 (HSP)
│ │ └── src/main/ets/
│ │ ├── HttpClient.ets # HTTP 封装
│ │ └── ApiInterceptor.ets # 拦截器
│ ├── ability_storage/ # 数据存储 (HSP)
│ │ └── src/main/ets/
│ │ ├── RdbHelper.ets # RDB 封装
│ │ └── PreferencesHelper.ets
│ └── ability_security/ # 安全能力 (HSP)
│ └── src/main/ets/
│ └── CryptoUtil.ets
│
├── common/ # 公共层 (HAR)
│ └── src/main/ets/
│ ├── components/ # UI 组件
│ │ ├── LoadingView.ets
│ │ └── ErrorView.ets
│ ├── model/ # 数据模型
│ │ ├── Product.ets
│ │ └── User.ets
│ ├── constants/ # 常量
│ │ └── AppConstants.ets
│ └── utils/ # 工具类
│ └── DateUtils.ets
│
├── build-profile.json5 # 工程构建配置
└── oh-package.json5 # 依赖管理
五、模块间通信
分层之后,模块之间不能直接调用对方内部实现。需要通过标准化接口通信。
方式一:接口定义在公共层
在 common 层定义接口,各层实现:
// common/src/main/ets/interfaces/INetworkService.ets
export interface INetworkService {
get<T>(url: string): Promise<T>;
post<T>(url: string, data: object): Promise<T>;
}
能力层实现接口:
// abilities/ability_network/src/main/ets/NetworkService.ets
import { INetworkService } from '@app/common';
export class NetworkService implements INetworkService {
async get<T>(url: string): Promise<T> {
const http = await import('@ohos.net.http');
const client = http.createHttp();
const res = await client.request(url, { method: http.RequestMethod.GET });
return JSON.parse(res.result as string) as T;
}
async post<T>(url: string, data: object): Promise<T> {
const http = await import('@ohos.net.http');
const client = http.createHttp();
const res = await client.request(url, {
method: http.RequestMethod.POST,
extraData: JSON.stringify(data),
header: { 'Content-Type': 'application/json' }
});
return JSON.parse(res.result as string) as T;
}
}
特性层通过依赖注入使用:
// features/feature_home/src/main/ets/HomePage.ets
import { INetworkService } from '@app/common';
@Entry
@Component
struct HomePage {
@State products: Product[] = [];
private networkService: INetworkService;
aboutToAppear() {
this.loadData();
}
async loadData() {
try {
this.products = await this.networkService.get<Product[]>('/api/products');
} catch (err) {
console.error('加载失败: ' + (err as Error).message);
}
}
build() {
// ...渲染列表
}
}
方式二:事件总线
跨模块事件通知用 Emitter(参考第 47 篇调试技巧中提到的事件机制):
// common 层定义事件常量
export const EVENT_CART_UPDATED = 'cart_updated';
export const EVENT_LOGIN_SUCCESS = 'login_success';
// 购物车模块发送事件
import { emitter } from '@kit.BasicServicesKit';
emitter.emit({ eventId: EVENT_CART_UPDATED }, { data: { count: 3 } });
// 首页模块监听
emitter.on({ eventId: EVENT_CART_UPDATED }, (eventData) => {
console.info('购物车数量更新: ' + eventData.data.count);
});
方式三:AppStorage 全局状态
跨模块共享简单状态用 AppStorage:
// 登录模块写入
AppStorage.setOrCreate('userInfo', { id: '123', name: '张三' });
// 任意模块读取
const userInfo = AppStorage.get('userInfo');
AppStorage 只适合轻量数据。数据量大或结构复杂时用 RDB(第 23 篇)或分布式 KV(第 62 篇)。
六、依赖注入容器
大型应用需要统一的依赖注入容器,避免每个模块手动创建实例:
// common/src/main/ets/DIContainer.ets
export class DIContainer {
private static instances: Map<string, object> = new Map();
static register<T>(key: string, instance: T): void {
this.instances.set(key, instance as object);
}
static resolve<T>(key: string): T {
return this.instances.get(key) as T;
}
static unregister(key: string): void {
this.instances.delete(key);
}
}
// 在 EntryAbility 中初始化
import { DIContainer } from '@app/common';
import { NetworkService } from '@app/ability_network';
import { RdbHelper } from '@app/ability_storage';
export default class EntryAbility extends UIAbility {
onCreate() {
// 注册全局依赖
DIContainer.register('INetworkService', new NetworkService());
DIContainer.register('IRdbHelper', new RdbHelper());
}
}
各模块通过容器获取依赖:
const networkService = DIContainer.resolve<INetworkService>('INetworkService');
七、多设备适配
产品定制层负责设备差异化。通过条件编译和设备类型判断,实现多设备适配:
// entry/src/main/ets/pages/MainPage.ets
import { deviceInfo } from '@kit.BasicServicesKit';
@Entry
@Component
struct MainPage {
@State deviceType: string = '';
aboutToAppear() {
this.deviceType = deviceInfo.deviceType;
}
build() {
if (this.deviceType === 'phone') {
// 手机竖向布局
Column() {
this.contentArea()
}
} else if (this.deviceType === 'tablet') {
// 平板左右分栏
Row() {
this.sideBar()
this.contentArea()
}
}
}
@Builder contentArea() { /* ... */ }
@Builder sideBar() { /* ... */ }
}
八、常见误区
九、从单模块迁移到分层架构
如果你的应用已经是一个大 entry 模块,迁移步骤:
先拆公共层。把工具类、常量、数据模型抽到 HAR,entry 模块引用它。这一步风险最低。
再拆能力层。把网络、存储、日志等通用能力封装成 HSP,通过接口暴露给上层。
然后拆特性层。按业务域划分 feature 模块,每个 feature 包含自己的页面和业务逻辑。
最后整理产品层。entry 只保留入口、路由编排和设备适配。
每一步迁移完做一轮回归测试,确保功能没退化。
十、小结
分层架构不是"看起来整齐",而是"改起来安全"。四层架构的核心原则就一条:依赖只能向下,不能向上。公共层不知道业务,能力层不知道 UI,特性层不知道彼此存在。违反这条原则,架构就会退回到面条代码。
下一篇我们讲错误体系标准化。分层之后,错误如何在层间传递、如何统一采集、如何不丢失上下文——这是分层架构能不能用的关键。