【寻迹校园 HarmonyOS NEXT 实战 02】多模块工程拆解:entry、HAR 与 HSP 如何分工
这是"寻迹校园 HarmonyOS NEXT 实战"系列第 2 篇。本文不从概念定义出发,而是结合一个真实 ArkTS 项目,说明页面壳、共享 UI、核心模型和业务服务如何拆成 entry、HAR 与 HSP。

上图为本文原创生成的多模块架构概念图:中心应用壳负责装配入口,两侧模块承载可复用能力,底部共享服务提供统一支撑。它只表达工程边界,不是界面截图。
一、为什么不能把所有代码都放进 entry
小型 HarmonyOS NEXT 示例通常只有一个 entry 模块。页面少时这种结构很直接,但"寻迹校园"逐步增加了发布、匹配、详情、认领、交接、举报、消息、设置和小艺入口,页面数量达到 18 个,继续把模型、颜色、数据库和系统能力全塞进 entry 会产生几个问题:
- 页面可以随意越过 Service 直接读写数据库;
- 不同页面复制同一份状态枚举和筛选词表;
- 公共组件依赖业务页面,复用方向反转;
- 测试纯规则时被 UIAbilityContext 和系统 Kit 阻塞;
- 多模块安装时难以判断旧代码来自 entry 还是共享模块。
因此项目按"谁拥有这份代码"拆成四个模块,而不是按页面数量机械拆包。
二、当前模块拓扑
根工程的模块清单经过脱敏后可以简化成:

左侧表示 HAR 在编译期进入应用包,右侧表示多个应用模块在运行时访问同一个共享服务。先区分"打进包里复用"与"运行时共享",再决定模块类型,能避免只按目录多少机械拆分。
json5
{
"app": {
"products": [
{
"name": "default",
"compatibleSdkVersion": "5.0.0(12)",
"targetSdkVersion": "6.1.1(24)",
"runtimeOS": "HarmonyOS"
}
]
},
"modules": [
{ "name": "entry", "srcPath": "./entry" },
{ "name": "common_ui", "srcPath": "./common-ui" },
{ "name": "common_core", "srcPath": "./common-core" },
{ "name": "shared_business", "srcPath": "./shared-business" }
]
}
公开文章中不要直接复制完整 build-profile.json5。真实工程可能包含本机证书路径和调试签名材料,示例只保留模块和 SDK 相关字段。
整体依赖方向如下:
text
entry
├─ depends on common-ui HAR
├─ depends on common-core HAR
└─ depends on shared-business HSP
shared-business HSP
└─ depends on common-core HAR
common-ui HAR
└─ 不依赖业务页面和 Repository
common-core HAR
└─ 不依赖 ArkUI 页面和具体数据源
三、entry:应用壳、路由和页面组装
entry 负责用户真正看到的页面,以及应用启动和导航组合:
text
entry/src/main/ets/
├─ entryability/
│ └─ EntryAbility.ets
├─ components/
│ ├─ BottomNavigation.ets
│ ├─ SideNavigation.ets
│ ├─ TopNavigation.ets
│ └─ ReportMedia.ets
└─ pages/
├─ Index.ets
├─ HomePage.ets
├─ PublishFormPage.ets
├─ MatchResultsPage.ets
├─ ClaimReviewPage.ets
├─ HandoffPage.ets
└─ ...
EntryAbility 只做启动窗口和首页加载:
ts
export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/Index', (error) => {
if (error.code) {
hilog.error(DOMAIN, TAG, 'loadContent failed: %{public}s', JSON.stringify(error));
return;
}
hilog.info(DOMAIN, TAG, '%{public}s', 'Index loaded');
});
}
}
真正的业务初始化、Tab 切换和 NavPathStack 由 Index.ets 组装。这里的关键原则是:entry 可以调用业务服务,但不拥有数据库 Schema 和业务状态机。
四、common-ui HAR:主题 Token 与无业务组件
common-ui 是共享 UI 资产层,主要包含:
AppColors:品牌、页面、文字、边框和状态语义色;AppSpacing:4、8、12、16、24、32 等间距;AppSizes:44vp 触控目标、48vp 控件、52vp 主按钮;AppTypography:Display、Title、Body、Caption 字阶;AppIcon、StatusPill等可复用组件;- 浅色和深色资源。
例如页面只使用语义 Token:
ts
Text('信息相似分')
.fontSize(AppTypography.CAPTION)
.fontColor(AppColors.TEXT_SECONDARY)
Button('查看详情')
.height(AppSizes.CONTROL)
.fontColor(AppColors.BRAND_PRIMARY)
.backgroundColor(AppColors.BRAND_CONTAINER)
公共 UI 不应该知道什么是 ClaimStatus,也不应该直接调用 ReportService。如果组件必须读取业务数据,它就不再是纯公共组件,应留在 entry 或明确的 feature 模块中。
五、common-core HAR:稳定模型、路由和纯规则
common-core 存放可被页面和业务层共同依赖的稳定契约:
text
common-core/src/main/ets/
├─ models/
│ ├─ AppContracts.ets
│ ├─ ItemReport.ets
│ └─ ReportTaxonomy.ets
├─ navigation/
│ └─ AppRoute.ets
└─ policies/
└─ ReportFilterPolicy.ets
这里适合放:
ReportType、ReportStatus、ClaimStatus等枚举;ItemReport、ReportDraft、MatchCandidate等模型;- 类型化路由参数;
- 发布和筛选共享词表;
- 不依赖平台的纯筛选规则;
OperationResult<T>结果契约。
这里不适合放:
- RelationalStore 的建表 SQL;
- Photo Picker 调用;
- 页面按钮文案和布局;
- 依赖
UIAbilityContext的初始化。
把 ReportFilterPolicy.ets 放在 common-core 后,Node 本地测试可以直接执行生产纯规则,不需要启动模拟器,也不需要复制一份 JavaScript 版本的筛选逻辑。
六、shared-business HSP:Service、Repository 与系统能力适配
shared-business 承担真正的业务和数据所有权:
text
shared-business/src/main/ets/
├─ services/
│ ├─ ReportService.ets
│ ├─ ClaimService.ets
│ ├─ HandoffService.ets
│ ├─ ModerationService.ets
│ ├─ PhotoPickerService.ets
│ └─ XiaoYiAgentAdapter.ets
└─ repositories/
├─ ReportRepository.ets
├─ ReportDraftRepository.ets
├─ ReportPhotoRepository.ets
├─ ClaimRepository.ets
└─ ...
Service 与 Repository 的边界非常明确:
| 层 | 负责 | 不负责 |
|---|---|---|
| Service | 校验、匹配、状态迁移、跨 Repository 协调 | ArkUI 布局、原始 SQL |
| Repository | CRUD、Schema、迁移、数据源映射 | 按钮状态、业务文案决策 |
| Capability Adapter | Photo Picker、Agent Framework 等平台调用 | 重复实现业务规则 |
HSP 的价值不只是"共享代码"。对当前项目而言,它让多个页面使用同一份业务服务和数据源,也让多模块安装时必须认真处理 HAP 与 HSP 的版本一致性。
七、为什么多模块项目优先构建完整 APP
如果只修改 entry 页面,构建和覆盖安装 entry HAP 通常足够。但一旦改动 shared-business,设备上旧 HSP 可能继续存在,导致"页面是新版本、业务服务是旧版本"的错觉。
因此项目提供两个脚本:
powershell
# 完整多模块构建,日常主验证路径
.\scripts\build-app.ps1
# 只检查 entry HAP 时使用
.\scripts\build-hap.ps1
真机覆盖安装时也要确认 entry HAP 和依赖 HSP 是否同步更新。构建成功只证明产物生成,不证明设备上的模块组合已经替换成功。
八、模块拆分后的收益
这套结构带来了四个直接收益:
- 页面无法轻易绕过 Service 直接持久化;
- 发布和筛选共用相同模型与词表;
- 纯规则和状态机可以在 Node 环境快速回归;
- UI、业务和平台能力的失败边界更容易描述。
它也带来一个成本:构建、签名和安装链路变长。HAR、HSP 和 HAP 的版本关系必须记录清楚,不能把单模块经验直接套到多模块工程。
九、何时不应该急着拆模块
如果项目只有两三个页面、没有共享业务、没有独立数据层,也没有复用计划,过早拆 HAR/HSP 只会增加配置和构建成本。更稳妥的判断标准是:
- 是否出现两个以上调用方需要同一份契约;
- 是否需要隔离 ArkUI 与业务规则;
- 是否需要单独测试纯规则;
- 是否需要复用共享 UI 或业务能力;
- 是否已经出现依赖方向混乱。
模块化的目标是建立所有权边界,而不是让目录看起来更"高级"。
十、本文小结
"寻迹校园"把页面壳放在 entry,把主题和无业务组件放在 common-ui HAR,把模型与纯规则放在 common-core HAR,把 Service、Repository 和能力适配器放在 shared-business HSP。整个工程只允许依赖从页面向业务、再向数据源流动。
下一篇将继续拆解路由:如何用 AppRoute、类型化参数和一个 NavPathStack 管理 18 个业务页面,并同时支持手机、平板和大屏 Shell。
系列导航:第 2 篇 / 共 50 篇。上一篇:《从校园痛点到可上架 MVP》;下一篇:《NavPathStack 路由实战:18 个页面如何集中管理》。