【寻迹校园 HarmonyOS NEXT 实战 02】多模块工程拆解:entry、HAR 与 HSP 如何分工

【寻迹校园 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 切换和 NavPathStackIndex.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 字阶;
  • AppIconStatusPill 等可复用组件;
  • 浅色和深色资源。

例如页面只使用语义 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

这里适合放:

  • ReportTypeReportStatusClaimStatus 等枚举;
  • ItemReportReportDraftMatchCandidate 等模型;
  • 类型化路由参数;
  • 发布和筛选共享词表;
  • 不依赖平台的纯筛选规则;
  • 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 是否同步更新。构建成功只证明产物生成,不证明设备上的模块组合已经替换成功。

八、模块拆分后的收益

这套结构带来了四个直接收益:

  1. 页面无法轻易绕过 Service 直接持久化;
  2. 发布和筛选共用相同模型与词表;
  3. 纯规则和状态机可以在 Node 环境快速回归;
  4. 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 个页面如何集中管理》。

相关推荐
用户0934077735142 小时前
HarmonyOS WPS Open SDK 实践:用 SdkConstants 判断当前 HAR 形态
harmonyos
独守一片天3 小时前
HarmonyOS鸿蒙新生态智能体意图框架怎么设计?
华为·harmonyos
梦想不只是梦与想15 小时前
鸿蒙应用api的兼容性:参数配置(二)
harmonyos·sdk版本·sdkversion
北墨NoLimit20 小时前
鸿蒙线程间通信怎么选:TaskPool、TaskGroup、LongTask 与 Worker 实战
typescript·harmonyos
woshihuanglaoshi1 天前
错题四科入库:鸿蒙错题本种子数据与复习队列效果
学习·华为·harmonyos·鸿蒙
kiros_wang1 天前
鸿蒙ArkTS枚举实战|静态枚举、动态枚举业务选型、规范落地与避坑全解
harmonyos
2501_919749031 天前
华为鸿蒙免费音乐APP—小羊免费音乐
华为·harmonyos·鸿蒙
世人万千丶1 天前
物品借还闭环:鸿蒙物品清单种子数据与清单效果
学习·华为·harmonyos·鸿蒙
贾伟康1 天前
【知律|10】HarmonyOS ArkTS 案例边界实战:明确普法内容不替代法律意见
harmonyos·arkts·arkui·应用合规·内容治理