HAP 从 38MB 压到 11MB:一次鸿蒙应用包体积瘦身的完整记录

举报
yd_274993192 发表于 2026/11/11 10:32:42 2026/11/11
【摘要】 上线前发现安装包 38MB,用户下载转化明显偏低。本文按「先量、再砍、最后验证」的顺序完整记录这次瘦身:用 unzip 拆开 HAP 看清资源/so/字节码的真实占比,再依次处理图片格式与尺寸收敛、字体子集化、abiFilters 只留 arm64-v8a、HSP 动态共享包与 HAP 按需加载、ArkGuard 混淆与 tree-shaking、构建产物里的重复依赖与调试残留。每一步都给出前后体

包体积这件事,平时没人管,直到上线前运营给了一张图:应用市场里详情页到下载的转化,和安装包大小高度相关。我们的 HarmonyOS 应用当时是 38MB,对工具类 App 来说偏胖。花了两周做瘦身,最后到 11MB,而且首屏启动顺带快了 400ms。

这篇不复述「图片用 webp」这种结论,而是按实际动手的顺序走一遍:先建立一个能看懂的体积账本,再按收益从高到低砍,最后用启动耗时和功能回归确认没砍坏东西。文中的命令和配置基于 DevEco Studio + Stage 模型的工程结构,具体字段名请以你所用 SDK 版本为准。

一、先量一次:38MB 到底花在哪

不看账本就优化,等于闭眼删代码。HAP 本质上是个 zip,直接拆开看目录分布是最快的方式:

# 解包,按体积倒序列出目录
unzip -q entry-default-signed.hap -d hap_out
du -sh hap_out/*
du -ah hap_out | sort -rh | head -30

第一次拆开的结果很说明问题(总计 38.2MB):

目录/内容 体积 占比
resources/base/media(图片、动效) 16.4MB 43%
libs/arm64-v8a(so) 8.1MB 21%
resources/rawfile(离线数据、字体) 5.6MB 15%
ets/modules.abc(ArkTS 字节码) 4.2MB 11%
其余(配置、map、杂项) 3.9MB 10%

结论很清楚:图片占了一半,so 拿了五分之一,而大家最爱讨论的「代码体积」只排第三。这也决定了后面的优化顺序——先动资源和 so,收益最快。

顺手做了个可持续的事:把这条 du 统计写进 CI,每次构建后输出体积报告并与上次对比。后面每一步的收益都是从这张表里读出来的,不靠感觉。

二、动作一:图片从 16.4MB 到 3.1MB

16.4MB 的图片里,真正的问题不是格式,而是尺寸和冗余。挑三个典型:

  • 一张 1024×1024 的引导页插画,实际显示区域是 240dp;
  • 同一个图标在 base、dark、xxhdpi 三个目录各存了一份 PNG;
  • 三个 1.5MB 的 Lottie JSON,页面里其实只播其中一个。

处理顺序如下:

  1. 尺寸收敛:按目标设备的实际像素密度反推所需分辨率,插画从 1024 降到 480,单张 1.4MB → 210KB。这一步比换格式省得多,因为像素数直接减了四倍。
  2. 格式替换:静态大图统一转 WebP,质量 82。HarmonyOS 对 WebP 支持完整,一张 210KB 的 PNG 转完约 68KB。图标类改 SVG,矢量在任意密度下都清晰,还省掉了多套位图。
  3. 位图去重:按文件 MD5 扫一遍资源目录,找出内容完全相同的多份拷贝,合并成一个资源引用。这一步清掉了 2.7MB,纯属历史遗留。
  4. 动效按需:三个 Lottie JSON 里两个只在「首次使用引导」出现,移到按需下载的 rawfile 分片里,首包不再包含。

这里有个容易踩的点:别用脚本批量转格式就完事。WebP 对渐变和半透明阴影的处理在低质量下会有色带,插画类素材建议逐张看一遍,必要时单独提到 90。我们第一轮图省事全用 75,结果登录页的背景渐变出现明显条纹,返工了一轮。

三、动作二:字体子集化,砍掉 3.8MB

rawfile 里塞了一个完整的中文字体包,3.8MB。全量中文字体动辄十几 MB,直接打包是最常见也最亏的做法。两条路:

  • 用系统字体:如果只是「想好看一点」,优先用系统提供的字体族,零成本。
  • 子集化:确实需要品牌字体时,用 fonttools 只保留实际用到的字符集。
# 收集代码里出现的所有中文文案,导出字符集
subfont -i "常用文案字符.txt" custom.ttf -o custom-subset.woff2

我们是混合方案:正文和绝大多数 UI 用系统字体,只有品牌标题保留定制字体,并做了子集化(只含标题用到的 200 多个字),3.8MB → 96KB。

子集化的代价要提前想清楚:动态文案会缺字。用户昵称、评论内容这类运行时才出现的字符,一定会落到子集之外。所以子集字体只能用在「文案可控」的位置,凡是用户生成内容,一律走系统字体。这一条如果没想明白,上线后会出现零星的方框字,排查起来很痛苦。

四、动作三:so 只留一个 ABI,8.1MB → 3.4MB

8.1MB 的 so 里,一半是重复的架构。工程默认会把 arm64-v8a 和 armeabi-v7a 都打进去,而当前鸿蒙设备已经基本不需要 32 位:

// entry/build-profile.json5
{
  "buildOption": {
    "externalNativeOptions": {
      "abiFilters": ["arm64-v8a"]
    }
  }
}

只这一行,体积少了 4.1MB。同时注意两件事:

  • so 要 strip:Release 构建默认会去掉符号表,但如果你自己接了 CMake,确认 -s 或 strip 步骤在流水线里真的执行了。我们有一个自研 so 因为构建脚本覆盖了参数,带了完整调试符号,单文件 2.3MB。
  • so 也能按需:只有特定功能(比如 OCR、音视频解码)才用到的 so,可以考虑拆到 HSP 或动态加载,而不是全塞首包。

五、动作四:HSP 与 HAP 按需加载,砍掉 4.6MB 首包

Stage 模型给了两种拆分手段,很多人只知道 HSP 不知道 HAP 也能拆:

方式 适用场景 加载时机
HSP(动态共享包) 多个 HAP 共用的代码与资源 随宿主一起加载,不能独立安装
HAR(静态共享包) 编译期复用的工具库 打进调用方,不减少体积
Feature HAP 低频功能模块(设置、帮助中心、活动页) 按需下载安装

我们把「活动页、帮助中心、扫描」三个低频模块拆成 Feature HAP,首包只留主流程。首包减少 4.6MB,代价是首次进入这三个入口时会有一小段下载等待——所以入口处必须有明确的加载态,不能白屏。

这里最容易被忽略的是HSP 的共享边界。把一段代码放进 HSP,只有被两个以上 HAP 引用时才真正省体积;如果只有一个 HAP 用,放进 HSP 反而多了一层包结构和一次加载开销。我们起初把只有主包用的网络库抽进 HSP,白折腾了一场,最后挪回去了。

六、动作五:字节码从 4.2MB 到 2.6MB

ArkTS 编译成 modules.abc,这部分靠三件事压:

  1. 开启混淆:在 build-profile.json5 里启用 ArkGuard,移除无用代码、缩短标识符。我们的项目 4.2MB → 2.9MB。要注意配置 -keep 白名单,反射调用的类名、JSON 序列化的字段名被混淆后会直接崩,这类问题只在 Release 包出现,务必用 Release 包跑一遍主流程回归。
  2. 清掉调试残留:把 console.log 和自研的 debug 工具模块在 Release 构建里条件编译掉。我们代码里散着 400 多处日志,光字符串常量就占了 300 多 KB。
  3. 去掉 sourcemap 与测试资源:确认 Release 产物里没有 .map,也没有 mock 数据和测试用例引用的 fixture 文件。这一项清出 1.1MB。

还有一个隐蔽的漏点:三方 SDK 的重复依赖。同一个网络库被三个 SDK 各自以不同版本引入,构建时没报错,产物体积却叠了三份。用 ohpm ls 或构建日志里的依赖树核对一遍,把版本对齐,能省下意料之外的一块。

七、动作六:别忽视「看不见」的配置

有两类配置项不占目录体积,却实实在在影响用户下载量:

  • 压缩级别:HAP 打包本身是 zip,确认构建配置里没有把压缩关掉。我们迁移构建脚本时误留了一个「不压缩」的开关,产物比正常大了 6MB。
  • 应用市场分包:华为应用市场支持按设备能力分发,把不同分辨率、不同 ABI 的资源分开投放,用户实际下载的会比本地构建产物更小。上线时把这一项打开,配合前面的 ABI 收敛,效果叠加。

八、效果汇总与验证

七个动作按收益排序如下:

动作 节省 改造成本 风险
图片尺寸收敛 + 格式替换 + 去重 13.3MB 中 低(需逐张目检)
Feature HAP 拆分 4.6MB 高 中(需处理下载态)
so 只留 arm64-v8a 4.1MB 低 低
字体子集化 3.7MB 中 中(动态文案缺字)
ArkGuard 混淆 + 清调试残留 2.7MB 中 中(反射/序列化)
依赖去重 + 清测试资源 1.5MB 低 低
确认压缩开启 — 低 低

最终 38.2MB → 11.3MB,降幅 70%。但瘦身不能只看体积,我们做了三项回归:

  1. 功能回归:全部用 Release 签名包跑,重点覆盖被混淆的反射路径、三个 Feature HAP 的首次下载、以及子集字体相关的标题页。
  2. 启动耗时:冷启动从 1.42s 降到 1.01s。主要收益来自首包 so 减少和图片解码量下降,不是代码变小——这点值得记住,体积和启动不是同一个指标。
  3. 低端机实测:在内存较小的设备上验证 Feature HAP 下载与加载,确认没有因为分片反而变慢。

九、哪几件事其实不值得做

踩过之后,有几条反常识的结论:

  • 不要在项目初期就做极致分包。HAP 拆分带来的构建复杂度、下载态处理、跨包状态共享问题,会持续消耗日常开发效率。首包超过 20MB 再考虑拆。
  • 不要为了体积牺牲可读性去手写混淆规则。ArkGuard 的默认规则已经能拿到大部分收益,过度定制规则带来的线上崩溃排查成本远高于省下的几百 KB。
  • 不要把所有图片都转 WebP。小尺寸图标转 SVG 更划算,而需要透明通道又色彩简单的图形,PNG-8 有时反而更小。逐类判断比一刀切有效。
  • 不要忽视「体积账本」本身。这次最有价值的产出其实不是那 27MB,而是 CI 里那张每次构建都会更新的体积报告——它让下一次的膨胀在发生时就可见,而不是等到上线前才发现。

最后回到那张转化率曲线:包体积从 38MB 降到 11MB 之后,详情页到下载的转化提升了大约 9 个百分点。这个数字比任何技术指标都更能说明,包体积是一项产品指标,不只是工程指标。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。