HarmonyOS 应用包体积优化:从 HAP 结构到资源与 so 裁剪
一、先搞清楚体积花在哪
HarmonyOS 应用上架时提交的是 App Pack(.app),安装到设备上的单位是 HAP。一个典型的工程里,体积主要来自四块:
| 组成 | 位置 | 常见占比 |
|---|---|---|
| 字节码与 JS bundle | ets/、modules.abc | 20%~40% |
| 原生库 | libs/<abi>/*.so | 20%~50%(有 C++ 依赖时更高) |
| 资源 | resources/、resources.index | 15%~35% |
| 三方 HAR/HSP | 依赖打包产物 | 视依赖而定 |
优化前必须先量一遍。把构建产物里的 HAP 后缀改成 .zip 解出来,逐目录看大小:
unzip -l entry-default-signed.hap | sort -k1 -n -r | head -30
不看这个列表就开始删东西,通常会删错方向——很多人第一反应是压图片,结果 libs 里躺着一个 40MB 的 libopencv.so。
二、so 裁剪:只留 arm64-v8a
这是投入产出比最高的一步。HarmonyOS 真机目前以 arm64-v8a 为主,但不少工程为了兼容模拟器,把 x86_64 也打进去了,体积直接翻倍。
在模块的 build-profile.json5 里显式指定 ABI:
{
"buildOption": {
"externalNativeOptions": {
"abiFilters": ["arm64-v8a"]
}
}
}
代价是模拟器跑不起来,因为开发机上的模拟器一般是 x86_64。折中方案是维护两套构建配置:日常调试用带 x86_64 的 debug,出包用只留 arm64-v8a 的 release。这点配置成本远比每次上传几十 MB 划算。
如果用的三方 SDK 提供了多个 ABI 的 so,同样要在依赖层做排除,而不是等打包完再删——后者会在签名校验时报错。
三、资源:不是「压一遍」就完事
资源优化最容易做成形式主义。真正有效的是三条:
1. 图片格式与尺寸对齐实际使用场景。
一张 3000×2000 的 banner 放在 360vp 宽的卡片里,无论压得多狠都是浪费。正确做法是按最大目标显示尺寸(含 2x/3x 倍图上限)导出,格式优先 WebP。HarmonyOS 的 Image 组件原生支持 WebP,不需要额外解码库。
2. 干掉重复与未引用资源。
多轮迭代后,resources/base/media 里通常会沉淀一批没人引用的图。DevEco Studio 的 Code Linter 能扫出未引用资源,构建时也会提示冗余项;resources/rawfile 下的离线包、示例数据最容易被忘记。
3. 按需分包,别把「可能用到」都塞进主包。
resources 里的大文件(教程视频、离线词库、初始模型)应该拆到独立 HSP,首次进入对应功能时再下载或按需加载。主包里只留启动路径真正需要的资源。
四、字节码与混淆:ArkGuard 的正确用法
release 构建默认会做一定程度的压缩,但混淆需要显式开启。在 build-profile.json5 中配置:
{
"buildOption": {
"arkOptions": {
"obfuscation": {
"ruleOptions": {
"enable": true,
"files": ["./obfuscation-rules.txt"],
"consumerFiles": ["./consumer-rules.txt"]
}
}
}
}
}
混淆能显著减小字节码体积,也会把类名、属性名改成短名。只要代码里有反射、序列化、动态属性访问,就必须配白名单,否则运行时报「找不到属性」。常见的需要保留的:
- 与后端 JSON 字段一一对应的数据类字段名;
JSON.parse后按字符串下标访问的属性;- 通过
router.pushUrl字符串匹配的页面路径; - 三方 SDK 要求保留的类与方法。
一个实用习惯:把「不能改名的字段」集中放在 @ObservedV2 之外的数据传输类里,并在 obfuscation-rules.txt 中按类名整体保留。散落在各处的 -keep-property-name 会让规则文件迅速失控。
五、拆包:HAP / HSP / HAR 怎么选
三者的定位经常被混淆:
- HAP:可独立安装的运行单元,一个应用至少一个 entry HAP;
- HSP:动态共享包,多个 HAP 可共享同一份代码与资源,运行时按需加载;
- HAR:静态共享包,编译期被引用方打进自己的产物,本质是「源码/资源复用」。
想减主包体积,靠的是 HSP,不是 HAR。把「非首屏功能」抽成 HSP 后,主包只保留壳与路由,用户首次进入该功能时才加载对应 HSP。
但 HSP 有两个硬约束必须提前想清楚:
- 版本一致性:HSP 与宿主 HAP 的版本号要匹配,热更新场景下版本错配会直接加载失败;
- 依赖不能循环:HSP 之间互相引用会导致构建期报错,拆包时按「功能域」切,不要按「工具类」切。
六、优化的验收标准
体积优化必须有数字对比,否则很容易做出「感觉小了」的错觉。建议固定三个观测点:
- 安装包大小:
release构建产物的.app体积; - 首屏加载耗时:冷启动到首帧的时间,拆包过度会把这个指标做坏;
- 运行时内存:HSP 按需加载后,峰值内存是否下降。
经验上,一个中等规模的鸿蒙应用做完上面四步(so 裁剪、资源治理、混淆、HSP 拆包),体积下降 30%~50% 是常见区间;但如果首屏耗时反而变长,说明拆得过头了——把启动路径上的模块拆成 HSP,等于把「一次加载」变成「两次加载 + 一次调度」。
七、小结
包体积优化不是「压缩图片」这一件事,而是按体积构成逐项处理:so 按 ABI 裁、资源按实际尺寸和引用关系清、字节码靠混淆、功能靠 HSP 拆。每一步都要有前后数字,并且盯着首屏耗时这个反向指标——只减体积不管启动速度,是另一种形式的体验倒退。
- 点赞
- 收藏
- 关注作者
评论(0)