【听见课堂 HarmonyOS NEXT 实战系列 02】Stage Model 多模块架构:entry、HAR 与 HSP 如何分工
【听见课堂 HarmonyOS NEXT 实战系列 02】Stage Model 多模块架构:entry、HAR 与 HSP 如何分工
当 HarmonyOS NEXT 项目从单页 Demo 发展成完整应用后,最容易出现的问题不是“代码写不出来”,而是页面、模型、主题、数据库和系统能力全部挤在 entry 模块里。短期看起来开发很快,后期却会遇到循环依赖、重复类型、组件难复用和安装产物关系不清等问题。
听见课堂从项目结构上把职责拆成四个模块:entry、common-core、common-ui 和 shared-business。这篇文章以真实工程为例,说明 Stage Model 下如何建立清晰、可验证的模块边界。

一、四个模块分别解决什么问题
项目的模块关系可以概括为:
entry
├─ depends on common-core
├─ depends on common-ui
└─ depends on shared-business
└─ depends on common-core
common-core 无业务 UI 依赖
common-ui 无业务数据依赖

各模块职责如下:
| 模块 | 主要职责 | 不应该放入的内容 |
|---|---|---|
entry |
UIAbility、页面外壳、交互状态、路由入口 | 数据库表结构、复杂聚合规则 |
common-core |
领域模型、路由常量、用户偏好 | 具体页面、设备能力实现 |
common-ui |
颜色、尺寸、背景等共享视觉资产 | 课程业务规则、数据库访问 |
shared-business |
Service、Repository、持久化、系统能力封装 | 页面布局和临时 UI 状态 |
边界越清楚,依赖方向越容易保持单向。尤其是 common-core,它应该成为其他业务模块都能理解的稳定语言,而不是反向依赖页面或数据库实现。
二、entry 应该轻,但不是空壳
entry 仍然负责 Stage Model 的应用入口。听见课堂在 EntryAbility.ets 中先初始化课堂数据,再加载首页内容:
onWindowStageCreate(windowStage: window.WindowStage): void {
this.initializeDataAndLoad(windowStage);
}
private async initializeDataAndLoad(windowStage: window.WindowStage): Promise<void> {
const usingRelationalStore: boolean = await initializeClassroomData(this.context);
hilog.info(DOMAIN, TAG, 'RelationalStore active: %{public}s',
usingRelationalStore ? 'true' : 'false');
windowStage.loadContent('pages/Index');
}
这里有两个设计点:
- 数据初始化属于进入应用前的基础工作,但具体数据库创建和种子数据不写在 Ability 内;
initializeClassroomData返回当前是否启用了 RelationalStore;如果创建失败,内部切换到内存 Repository,再继续加载页面,避免把一次持久化异常直接变成白屏。
Ability 还要承担前后台生命周期协调。例如项目在进入后台时释放手语相机资源,这类和应用生命周期直接相关的动作,放在入口层比散落在页面里更可靠。
三、HAR 与 HSP 不是同一种复用
在这个项目中,三个公共模块的产品形态并不完全相同:
common-core、common-ui适合作为 HAR,被编译进使用它们的模块;shared-business作为 HSP,承载可共享的业务与能力实现。
HAR 更接近编译期复用,使用方最终会包含相关代码;HSP 则是独立共享包,运行时由应用模块引用。两者的差别会直接影响安装和交付。
例如在全新设备上只安装 entry 产生的 HAP,而没有安装它依赖的 HSP,应用可能无法完成正常启动。因此真机验证不能只写一条“安装 HAP 成功”,还要记录产物之间的依赖顺序。
一个更稳妥的设备验证顺序是:
hdc list targets -v
hdc install -r .\shared-business\build\default\outputs\default\shared-business-default-signed.hsp
hdc install -r .\entry\build\default\outputs\default\entry-default-signed.hap
实际路径应以当前 hvigor 输出为准。命令写进文章不代表本次写作过程中已经重新安装,交付时仍要保留真实终端输出。
四、模块依赖应该由包配置明确表达
entry/oh-package.json5 中通过本地模块路径声明依赖:
{
"dependencies": {
"@heard/common-core": "file:../common-core",
"@heard/common-ui": "file:../common-ui",
"@heard/shared-business": "file:../shared-business"
}
}
而 shared-business 只依赖 common-core。这能避免业务层为了使用颜色或页面组件而反向依赖 UI 模块,也避免核心模型知道数据库或设备实现。
建议为每个模块保留一个统一导出入口,例如:
// common-core/src/main/ets/index.ets
export * from './models/ClassroomModels'
export * from './navigation/Routes'
export * from './preferences/UserPreferences'
统一出口能减少跨模块调用方对内部文件位置的依赖。未来移动内部目录时,只要公共导出保持稳定,页面代码通常不需要跟着大面积修改。
五、不要把“共享”理解成“什么都往里放”
公共模块最常见的失控方式,是出现一个不断膨胀的 utils 或 common。判断代码应该放在哪个模块,可以问三个问题:
- 它表达的是稳定领域概念,还是具体界面?
- 它是否需要系统上下文、数据库或设备能力?
- 其他模块依赖它后,会不会反向引入不需要的实现?
例如:
RouteId和TaskItem是稳定领域语言,放在common-core;AppColors与AppSizes是共享视觉 token,放在common-ui;RelationalClassroomRepository需要关系型数据库能力,放在shared-business;- 页面选择了哪个 Tab、弹窗是否展开,是短生命周期状态,留在
entry。
六、构建通过后,还要验证模块产物
多模块项目的验证至少包括以下内容:
ohpm install
.\hvigorw.bat assembleHap --mode module -p module=entry@default
.\hvigorw.bat assembleApp
除了命令是否返回成功,还要检查:
entry、HAR、HSP 是否都生成了预期产物;- HSP 和 HAP 的包名、版本与签名是否匹配;
- 全新设备上能否按正确顺序安装并冷启动;
- 从后台恢复时,相机、语音等资源是否按生命周期重新建立;
- 文章或交付说明中是否把“构建成功”和“真机运行成功”分开描述。
听见课堂现有结构已经体现了这四层职责,但模块边界仍需要通过持续审查来维护。只要页面开始直接访问数据库,或 common-core 开始依赖 ArkUI 页面,就说明依赖方向正在变坏。
总结
Stage Model 的多模块架构不是为了让目录看起来更专业,而是为了把生命周期、领域语言、视觉资产和业务实现分开管理。HAR 与 HSP 也不只是两种打包后缀,它们会影响依赖方式、产物结构和真机安装流程。
下一篇将沿着页面到数据源的路径继续深入,分析为什么 ArkUI 页面不应该直接操作数据库,以及 Service 与 Repository 如何共同保证业务口径一致。
更多推荐


所有评论(0)