HarmonyOS 模块化设计实战:HAP、HAR、HSP 到底咋选?
本文深入探讨了HarmonyOS应用开发中的模块化设计策略。文章首先分析了大型项目面临的代码膨胀问题,提出模块化是解决协作、复用和按需加载三大痛点的关键方案。然后详细解析了HarmonyOS的三种包类型:HAP(应用主体)、HAR(静态共享库)和HSP(动态共享库),并通过实际案例展示了它们在不同场景下的应用方式。特别强调了多模块引用相同HAR包时可能导致的性能问题,通过对比实验数据证明优化方案可
一、开场白:代码多了,咱们咋办?
干过大型项目的兄弟都懂,一个 App 搞到后面,代码量能把你压死。几十个功能模块,五六个团队同时开发,今天你改个接口,明天他动个组件,后天整个应用就崩了。
我们一开始做 HarmonyOS 项目也踩过这个坑。entry 目录里堆了几百个文件,找个功能得翻半天。编译一次十几分钟,改个小按钮得等半天。最要命的是,不同团队代码搅在一起,谁也不敢随便动,怕把别人的功能搞挂了。
后来琢磨明白了,这玩意儿得模块化。但 HarmonyOS 的模块化跟你想的不太一样,它有三个包类型:HAP、HAR、HSP。刚开始我们也懵,这仨玩意儿到底啥区别?啥时候用哪个?
今天咱们就把这事儿整明白。从官方文档出发,结合实际场景,聊聊模块化设计到底咋搞。
二、为啥要模块化
2.1 HarmonyOS 的包结构是咋回事
在 HarmonyOS 里,应用包结构分三个阶段:开发态、编译态、发布态。简单说就是:
- 开发态:你们写代码时的工程结构
- 编译态:编译后生成的中间产物
- 发布态:最终上架到应用市场的.app 文件
核心就三个玩意儿:
HAP(HarmonyOS Ability Package):应用的主体,分两种:
- Entry 类型:应用的主入口,一个应用只能有一个
- Feature 类型:独立功能模块,可以有多个
HAR(HarmonyOS Archive):静态共享库,编译时会被打包进 HAP 里
HSP(HarmonyOS Shared Package):动态共享库,运行时按需加载
这仨东西的关系,看下图就明白了:
<
2.2 模块化设计的核心诉求
为啥要模块化?说白了就三个原因:
第一,多团队协作。大项目不可能让一个团队从头干到尾,得拆成多个模块,不同团队负责不同模块,各干各的,互不干扰。
第二,代码复用。有些功能是通用的,比如网络请求、日志打印、工具类,这些玩意儿写一遍就行了,别每个模块都抄一份。
第三,按需加载。不是所有功能用户都会用,有些低频功能可以做成按需加载,用户需要的时候再下载安装,省空间省流量。
2.3 UIAbility 组件设计影响模块化
这儿有个关键点很多人忽略了:你的应用是单任务单窗口,还是多任务多窗口?这直接影响模块化选型。
单任务单窗口:传统手机应用,一次只能干一件事。比如游戏应用,建议用单 HAP 承载 UIAbility。
多任务多窗口:大屏设备上,应用可以同时开多个窗口。比如:
- 笔记应用:用户可以同时打开多页笔记,互相复制内容
- 文档编辑:同时编辑多个文档,内容互相拖拽
- 导航应用:导航后台运行,前台可以查其他位置
- 视频应用:边看视频边浏览推荐列表
这种多窗口场景,每个窗口对应一个 UIAbility 实例,每个 UIAbility 可以单独显示一个窗口。这时候就得用多 HAP 了,每个 Feature 类型的 HAP 承载一个 UIAbility。
三、解决方案
3.1 共享模块咋搞
先说第一种场景:某个功能模块需要在多个应用之间共享。
比如你们公司有个网络库,十个 App 都要用。这时候咋办?总不能把代码抄十份吧?
正确做法是把这个网络库做成 HAR 包,发布到公司内部的 OHPM 仓。其他应用通过 ohpm install 安装,编译时自动打包进去。

这种能发布到 OHPM 仓的模块,只能是 HAR 包。HSP 不行,因为 HSP 跟应用的 bundleName 绑定,只能在一个应用内用,没法跨应用共享。
3.2 按需加载模块咋搞
第二种场景:某个功能用户不常用,想做成按需加载。
比如一个购物 App,有个"AR 试穿"功能,一个月没几个人用。这玩意儿要是打包在主包里,几百万用户下载时就得多花流量,多占空间。
按需加载有两个好处:
- 用户首次安装包体积小,下载快
- 安装后占用空间少,低频功能不占地方
实现按需加载,可以用 Feature 类型的 HAP 或者 HSP:
单 HAP 场景:如果应用只有一个 UIAbility,不需要 ExtensionAbility,优先用单 HAP(Entry 类型)。按需加载的模块用 HSP。
多 HAP 场景:如果要实现多任务多窗口,或者有 ExtensionAbility(比如卡片、分享),用多 HAP(一个 Entry + 多个 Feature)。每个 HAP 包含一个 UIAbility 或 ExtensionAbility。
3.3 多 HAP/HSP引用相同HAR包的坑
这儿有个大坑,我们踩过,你们得注意。
当多个 HAP 或 HSP 引用同一个 HAR 包时,HAR 里的单例会失效,影响启动性能。
看个例子:

HAR_COMMON 里有个 func 方法,执行一次要算很久:
// har_common/src/main/ets/utils/Utils.ets
const LARGE_NUMBER = 100000000;
function func(): number {
let count = 0;
while (count < LARGE_NUMBER) {
count++;
}
return count;
}
export let funcResult = func();
问题在于,当 HAP 和 HSP 同时引用 HAR_COMMON 时,func 方法会执行两次!因为 HAR 是静态库,会被分别打包进 HAP 和 HSP,各有一份副本。
咋解决?把 HSP 改成 HAR:

官方做了个性能对比:
| 方案 | 阶段时长 (毫秒) |
|---|---|
| 优化前(使用 HSP 包) | 3125 |
| 优化后(使用 HAR 代替 HSP) | 853.9 |
看到没?启动时间从 3 秒多降到 800 多毫秒,差了快 4 倍!


四、选型建议
4.1 单 HAP 工程咋选
如果你的应用是单窗口,只有一个 Entry 类型的 HAP,那选型就简单了。
不包含按需加载模块:全部用 HAR 就行。
](https://i-blog.csdnimg.cn/direct/c2fe88c108da41c6969c3efe439e7f59.png)
这样搞有三个好处:
- 全部编译进 HAP,没有额外的 HSP,节省安装和加载成本
- HAR 编译进 HAP 时,可以利用 ArkTS 的类型推断和编译优化
- 工程架构简单,后续演进灵活
包含按需加载模块:这时候得在 App Size 和启动性能之间做平衡。
App Size 优先:把公共依赖的模块封装在一个 HSP 模块壳里。

比如 hap_A 依赖于 har_A、har_C、har_D,hsp_B 依赖于 har_B、har_C、har_D。har_C 和 har_D 是公共的,为了节省空间,把它们封装到 common_hsp 里。
注意啊,这个 common_hsp 是个"模块壳",它本身没实际功能,就是为了把公共 HAR 包打包到一起,避免重复。
性能优先:直接依赖公共 HAR 包,不用 HSP 模块壳。

这样搞的话,har_C 和 har_D 会在 hap_A 和 hsp_B 里各有一份,App Size 会大一些,但启动性能更好,因为没有 HSP 的安装和加载损耗。
4.2 多 HAP 工程咋选
多 HAP 工程就是应用里有多个 HAP 包(一个 Entry + 多个 Feature)。这时候也得考虑有没有公共能力模块。
包含公共能力模块:同样得在 App Size 和启动性能之间做平衡。
性能优先:除了产品组件里的 HAP 包,其余全是 HAR 包。

这样搞的话,多个 HAP 之间会有相同的 HAR 包(比如 har_2、har_3、har_C 等)。App Size 会大一些,但如果 HAR 包本身不大,或者 App Size 不是瓶颈,可以用这个方案,减少动态加载的性能损耗。
App Size 优先:把公共的 HAR 包封装到 HSP 工程里。

比如有 3 个 HAP 包(1 个 entry 和 2 个 feature),把公共的 HAR 包封装到 common_wrap_hsp 和 feature_wrap_hsp 里。这两个 HSP 也是"模块壳",没实际功能,就是为了合理放置模块在编译产物中的位置。
这样搞的话,每个 HAR 包在 App 编译产物里只出现一次,App Size 最小。但模块壳数量别太多,否则影响安装速度和启动性能。
注意一个坑:在应用间共享的 HAR 包,原则上不允许依赖 HSP 包。因为 HSP 跟 bundleName 绑定,一旦 HAR 依赖了应用内 HSP,这个 HAR 就没法给其他应用共享了。
4.3 不包含公共能力模块
这种情况比较少,一般是一些小应用。可以参考单 HAP 的场景,全部用 HAR 就行。
五、避坑指南
坑 1:HSP 和 HAR 混用导致单例失效
前面说了,多 HAP/HSP 引用相同 HAR 包时,HAR 里的单例会失效。这个坑我们踩过,启动慢得离谱,后来用 Launch 模板一分析,才发现是这个问题。
咋避免?要么全用 HAR,要么把 HSP 改成 HAR。如果必须用 HSP,确保每个 HAR 只被一个模块引用。
坑 2:模块壳太多影响启动性能
模块壳是为了节省 App Size 搞出来的,但这玩意儿不能多用。每个 HSP 都要安装和加载,多了会影响启动性能。
建议:模块壳数量控制在 2-3 个以内,只封装真正公共的、体积大的 HAR 包。小的 HAR 包直接重复打包就行,影响不大。
坑 3:HAR 依赖 HSP 导致无法共享
这个坑更隐蔽。你搞了个 HAR 包想给多个应用用,结果这个 HAR 依赖了某个应用内的 HSP。编译没问题,但发布的时候发现,这个 HAR 没法给其他应用用了。
记住:要共享的 HAR 包,只能依赖其他 HAR 包,不能依赖 HSP。
坑 4:按需加载模块设计不合理
按需加载不是把随便哪个模块改成 HSP 就行。得考虑:
- 这个功能用户是不是真的不常用
- 按需加载后,用户体验会不会受影响
- 模块之间的依赖关系清不清晰
建议:把真正低频的、独立的功能做成按需加载,比如 AR 试穿、高级滤镜、离线地图这种。核心功能别搞按需加载,用户一打开 App 就要用的东西,必须打包在主包里。
坑 5:多窗口应用用单 HAP
如果你的应用要支持多任务多窗口(比如大屏设备上的分屏操作),别用单 HAP。每个窗口对应一个 UIAbility,每个 UIAbility 应该用独立的 Feature 类型 HAP 承载。
不然的话,用户开个分屏,两个窗口互相干扰,体验差得一批。
六、总结一下
模块化设计这事儿,说复杂也复杂,说简单也简单。核心就三点:
第一,搞清楚你的应用是啥形态。单窗口还是多窗口?要不要按需加载?有没有跨应用共享的需求?这决定了你们用 HAP、HAR、HSP 的哪个组合。
第二,在 App Size 和启动性能之间做平衡。想要包小,就用 HSP 模块壳;想要启动快,就全用 HAR。没有绝对的对错,看你的应用更在意哪个。
第三,避开那些坑。单例失效、模块壳太多、HAR 依赖 HSP、按需加载设计不合理、多窗口用单 HAP,这些坑我们都给你们标出来了,别踩。
最后送大家一句话:模块化不是一蹴而就的,得随着业务发展不断演进。一开始别搞太复杂,等真遇到瓶颈了再优化也不迟。
好了,今天叨了这么多,希望能帮你们把模块化这事儿整明白。有啥问题,评论区见。
更多推荐
所有评论(0)