HarmonyOS HAP 包体积治理:拆包审计、资源收口与模块取舍

应用功能没有明显增加,发布包却在几个迭代后增长了十几 MiB。仅凭“最近加了很多图片”去猜,往往会漏掉重复资源、调试文件、多个架构的原生库,以及被多个 HAP 重复打入的 HAR 内容。包体积治理必须从产物出发:先记录同口径基线,再拆开安装包统计组成,最后把每一次收口动作与构建结果对应起来。

本文不提供“删除一切大文件”的激进清单,而是建立一套可回归的 HAP 审计流程。读者可以沿着代码、资源、原生库和模块依赖四条线定位增长来源,并在 HAR 与 HSP 之间做有依据的选择。

请添加图片描述

一、包体积的第一原则:比较必须同口径

Debug 与 Release、不同产品、不同设备类型、是否开启混淆或压缩,都会改变产物。两个文件都以 .hap 结尾,不代表它们可以直接比较。

建立基线时至少记录:

字段示例含义
提交或版本对应哪次构建
产品与 targetphone、tablet 或具体产品
构建类型Release / Debug
SDK 与 DevEco Studio构建工具口径
HAP 名称entry 或 feature 模块
文件字节数不只记录四舍五入后的 MiB
解包后总字节数用于判断压缩影响
最大目录或文件快速定位变化

只有条件一致时,增量才有意义。首次治理不要先定“必须小于 20 MiB”,而应先回答当前包由什么组成、过去两次为何增长。

二、理解 APP、HAP、HAR 与 HSP 的关系

产物或模块作用体积治理关注点
APP用于分发的应用包集合包含哪些 HAP/HSP
HAP可安装和运行的能力模块代码、资源、依赖是否重复
HAR静态共享包使用方构建时内容可能并入产物
HSP动态共享包公共代码资源可独立复用

HAR 接入简单,适合通用组件和库;但当同一份大型 HAR 同时被多个 HAP 使用时,需要检查其内容是否在多个产物中重复。HSP 可以承载应用内共享代码与资源,帮助减少重复,但并非“换成 HSP 一定更小”:它会改变模块边界、依赖方式和运行组织,必须结合启动、发布、兼容和维护成本评估。

三、从安装包组成图建立审计方向

请添加图片描述

治理时把内容分为四组:

  1. ArkTS 与编译产物:业务代码、三方库、调试残留;
  2. 资源文件:图片、字体、音视频、多语言和重复变体;
  3. 原生库:.so、架构目录、符号和不需要的能力;
  4. 模块依赖:HAR/HSP、Feature HAP 和跨模块重复。

每组都要有“定位证据、改动动作、回归验证”。只看 HAP 总大小,无法判断一次优化究竟删除了什么。

四、先用官方解包能力得到可审计目录

华为开发者文档提供 HAP 解包工具与相关说明。实际操作时,应使用与当前 SDK/工具链匹配的官方能力解包,不要把网上未经核对的命令直接用于发布产物。

得到解包目录后,再用 PowerShell 生成文件清单:

$Root = "D:\package-audit\entry-release-unpacked"

$Files = Get-ChildItem -LiteralPath $Root -Recurse -File |
  ForEach-Object {
    [PSCustomObject]@{
      Path = $_.FullName.Substring($Root.Length)
      Extension = if ($_.Extension) { $_.Extension.ToLowerInvariant() } else { "[none]" }
      Bytes = $_.Length
      MiB = [Math]::Round($_.Length / 1MB, 3)
    }
  }

$Files |
  Sort-Object Bytes -Descending |
  Select-Object -First 30 |
  Format-Table Path, MiB -AutoSize

这个清单先回答“最大的 30 个文件是什么”。不要看到大文件就删除:地图、模型或启动资源可能本来就是核心能力,治理目标应是去除不必要、重复或错误打包的内容。

五、按扩展名汇总,判断增长来自哪一类

$ByType = $Files |
  Group-Object Extension |
  ForEach-Object {
    $Total = ($_.Group | Measure-Object Bytes -Sum).Sum
    [PSCustomObject]@{
      Extension = $_.Name
      Count = $_.Count
      Bytes = $Total
      MiB = [Math]::Round($Total / 1MB, 3)
    }
  } |
  Sort-Object Bytes -Descending

$ByType | Format-Table Extension, Count, MiB -AutoSize

扩展名汇总能快速发现字体、PNG、WebP、音频或 .so 是否占主导,但它不能代替目录分析。没有扩展名的编译产物、容器文件也可能很大,因此还要按一级目录汇总。

$Files |
  ForEach-Object {
    $Relative = $_.Path.TrimStart("\")
    $Top = ($Relative -split "\\")[0]
    [PSCustomObject]@{
      Top = $Top
      Bytes = $_.Bytes
    }
  } |
  Group-Object Top |
  ForEach-Object {
    [PSCustomObject]@{
      Directory = $_.Name
      MiB = [Math]::Round(
        (($_.Group | Measure-Object Bytes -Sum).Sum) / 1MB,
        3
      )
    }
  } |
  Sort-Object MiB -Descending

把两份统计保存为 CSV,后续迭代就能按类别比较,而不是只截一张总大小截图。

六、查重必须基于内容哈希,不要只看文件名

同一张图可能改了名称放在多个模块中,也可能同名但内容不同。使用 SHA-256 按内容分组:

$DuplicateGroups = Get-ChildItem -LiteralPath $Root -Recurse -File |
  Get-FileHash -Algorithm SHA256 |
  Group-Object Hash |
  Where-Object { $_.Count -gt 1 }

foreach ($Group in $DuplicateGroups) {
  $Paths = $Group.Group.Path
  $Size = (Get-Item -LiteralPath $Paths[0]).Length
  [PSCustomObject]@{
    Copies = $Group.Count
    OneFileMiB = [Math]::Round($Size / 1MB, 3)
    WasteMiB = [Math]::Round(
      ($Size * ($Group.Count - 1)) / 1MB,
      3
    )
    Paths = $Paths -join " | "
  }
}

“可节省空间”这里只是理论估算。资源是否能合并取决于模块访问边界、主题差异、设备限定和运行时加载方式。治理记录应注明最终采取的是删除、共用、按产品拆分,还是保留并说明原因。

七、资源收口要从使用场景开始

资源类常见问题包括:

  • 设计稿导出的 2 倍、3 倍图同时打入,但实际只用一个版本;
  • 同一字体包含多种字重,页面只使用其中一两种;
  • 示例视频、占位图和测试 JSON 进入 Release;
  • phone、tablet、wearable 的专属资源进入同一个目标产物;
  • 多语言文件复制了大段不需要的内容;
  • 可由矢量或绘制实现的简单背景被保存为大位图。

为每个候选资源建立审计行:

资源路径:
解包后字节数:
被哪些页面引用:
适用设备/产品:
是否存在内容重复:
替代方案:
删除后的功能回归点:

没有引用证据时,不要根据文件名猜测“没用”。动态资源名、反射式访问和 Web 内容可能不容易被普通文本搜索发现,需要结合运行路径验证。

八、图片优化不是统一降低质量

图片治理应先分类:

类型优先动作验证
图标和简单图形优先矢量或资源复用多尺寸边缘是否清晰
照片选择适合的有损格式和尺寸色带、细节和加载耗时
透明大图检查是否真的需要全尺寸透明通道透明边缘与内存
启动关键图兼顾体积与解码成本冷启动首帧
多设备背景按产品或 target 提供各设备不缺图

不要把所有 PNG 批量改成另一种格式后直接提交。格式支持、透明度、解码成本和视觉质量都要在目标设备检查;某些图片文件变小,却可能增加运行时解码压力。

九、字体和音视频通常是单文件大户

字体子集化可以显著减小包体积,但前提是明确字符范围。中文应用若只保留当前页面字符,用户输入、服务端文案或多语言内容可能显示为缺字。更稳的做法是:

  1. 统计实际字重和语言范围;
  2. 去除完全未使用的字体文件;
  3. 对可控展示文本评估子集;
  4. 在服务端动态文案、数字、符号和多语言下回归;
  5. 保留许可信息并遵守字体授权。

音视频同样需要先确认是否必须随包交付。可联网下载的内容可以迁移到按需资源,但要评估首用等待、离线能力、网络失败和审核要求,不能为了减小包体积破坏核心离线场景。

十、原生库先盘点架构和来源

从解包目录列出原生库:

$NativeLibraries = Get-ChildItem -LiteralPath $Root -Recurse -File |
  Where-Object { $_.Extension -eq ".so" } |
  Select-Object FullName, Length |
  Sort-Object Length -Descending

$NativeLibraries |
  ForEach-Object {
    [PSCustomObject]@{
      Path = $_.FullName.Substring($Root.Length)
      MiB = [Math]::Round($_.Length / 1MB, 3)
    }
  } |
  Format-Table Path, MiB -AutoSize

每个 .so 应能回答:来自哪个依赖、支持哪个架构、哪些功能调用、Release 是否需要、是否包含不应随包发布的符号或样例能力。不能因为某个架构目录体积大就直接删除,设备兼容范围和商店发布策略必须先确认。

十一、三方依赖治理要看“能力是否重复”

两个依赖可能分别提供 JSON、网络、图片或压缩能力,业务只用很小一部分。依赖审计表可以这样维护:

依赖引入模块实际调用能力是否与现有能力重叠去除验证
库 Aentry图片变换与系统能力部分重叠视觉回归、性能
库 Bfeature压缩无重叠解压兼容
库 C多个 HAP通用模型可能重复打包模块产物对比

不要为“以后可能用”长期保留大型依赖。删除依赖时同时检查 oh-package.json5、锁文件、import、构建配置和原生产物,避免只删声明却留下生成文件。

十二、HAR 与 HSP 的选择要用重复量证明

请添加图片描述

当多个 HAP 同时使用大型公共 HAR 时,先分别解包并计算重复文件或相同内容的总量。只有重复规模足够、模块边界稳定,才值得评估迁移 HSP。

决策问题偏向 HAR偏向 HSP
只有一个使用方通常没有共享收益
库很小且稳定迁移收益有限
多个 HAP 重复大型代码资源需警惕可评估共享
需要独立模块边界视项目而定更匹配共享组织
团队尚未验证启动和发布影响先保持不应只为数字迁移

迁移前后必须比较整个 APP,而不是只看某一个 HAP 变小。公共内容转移到 HSP 后,单 HAP 可能下降,但总安装包和运行表现才是最终指标。

十三、按产品和设备收口资源

多设备应用不应让每个目标携带全部设备专属资源。模块设计和构建配置应按实际产品拆分入口、页面和资源目录。具体 JSON5 字段会随工程结构和 SDK 演进,配置前应以当前项目 build-profile.json5、模块 module.json5 和官方多设备文档为准。

可以先建立资源归属清单:

{
  "phone": [
    "compact-navigation",
    "phone-onboarding"
  ],
  "tablet": [
    "two-pane-background",
    "keyboard-shortcuts"
  ],
  "shared": [
    "brand-icons",
    "core-localization"
  ]
}

这不是构建配置,而是审计输入。它迫使团队说明每个资源为何属于共享或专属,再据此修改真实 target 与资源目录配置。

十四、建立基线文件,让体积回归进入评审

把每次 Release 产物统计保存成 JSON:

{
  "version": "2.6.0",
  "product": "default",
  "buildMode": "release",
  "hapBytes": 28641280,
  "unpackedBytes": 51783240,
  "largestGroup": "resources",
  "largestFileBytes": 6241580
}

使用简单脚本比较总量和增量:

param(
  [string]$BaselinePath,
  [string]$CurrentPath,
  [long]$AllowedGrowthBytes = 1048576
)

$Baseline = Get-Content -LiteralPath $BaselinePath -Raw |
  ConvertFrom-Json
$Current = Get-Content -LiteralPath $CurrentPath -Raw |
  ConvertFrom-Json

if ($Baseline.product -ne $Current.product -or
    $Baseline.buildMode -ne $Current.buildMode) {
  throw "构建口径不一致,禁止比较"
}

$Growth = [long]$Current.hapBytes - [long]$Baseline.hapBytes
Write-Host ("HAP growth bytes: " + $Growth)

if ($Growth -gt $AllowedGrowthBytes) {
  throw "包体积增长超过预算,请附拆包明细"
}

预算不是禁止增长。新增核心能力可以合理增加体积,但提交者要说明增长来源、是否有替代方案、对下载与存储的影响,并更新认可后的基线。

十五、优化动作要逐项构建,避免无法归因

一次提交同时改图片格式、删除依赖、迁移 HSP、裁剪字体,最终即使包变小,也很难知道各动作贡献和风险。建议按下面顺序迭代:

  1. 保存原始 Release 包与解包清单;
  2. 删除明确无引用的样例和调试资源,重新构建;
  3. 合并内容完全相同的资源,重新构建;
  4. 调整图片、字体和音视频,逐类回归;
  5. 清理未使用依赖和原生库;
  6. 最后评估模块级 HAR/HSP 改造;
  7. 每一步记录 HAP、APP 和解包后大小。

这样出现视觉、启动或兼容回归时,可以快速定位到具体治理动作。

十六、常见误区与修复方向

误区为什么不可靠正确方向
只看 HAP 文件大小看不到 APP 总体和内部组成同时记录 APP、HAP、解包清单
大文件一律删除可能是核心资源先查引用和设备归属
所有图片统一降质视觉和解码成本不同按资源类型处理
HAR 全部迁移 HSP模块复杂度和运行成本被忽略用多 HAP 重复量证明收益
只在 Debug 对比发布口径不同固定 Release 产品构建
依赖声明删了就算完成产物可能仍有残留重建后拆包复核
单 HAP 变小就宣布成功内容可能转移到其他包比较完整 APP

十七、交付前验收清单

  • 基线与当前包使用相同产品、target、构建类型和工具链。
  • 已保存 HAP/APP 文件字节数与解包后清单。
  • 最大文件、扩展名汇总和一级目录汇总均已检查。
  • 重复资源按内容哈希定位,而不是只按文件名。
  • 每个删除或合并动作都有引用证据和功能回归项。
  • 字体子集覆盖动态文案、数字、符号和多语言。
  • 原生库的来源、架构和调用能力均可追溯。
  • 多 HAP 公共内容的重复规模已经量化。
  • HAR/HSP 决策比较的是完整 APP 与运行影响。
  • 包体积增长预算进入 Release 构建检查。
  • 优化后完成安装、启动、资源显示、离线和升级验证。

十八、用体积变更报告记录收益与代价

治理提交不能只写“包体积减小 5 MiB”。评审者需要知道减少来自哪里,是否移动到了其他 HAP/HSP,以及功能上付出了什么代价。建议每次提交附一份同口径报告:

构建口径:
- product / target:
- buildMode:
- SDK / DevEco Studio:
- 基线提交:
- 当前提交:

体积结果:
- APP:基线字节 -> 当前字节
- entry HAP:基线字节 -> 当前字节
- feature HAP:基线字节 -> 当前字节
- HSP:基线字节 -> 当前字节
- 解包总量:基线字节 -> 当前字节

变更归因:
- 删除未使用资源:
- 合并重复资源:
- 图片或字体调整:
- 依赖与原生库调整:
- 模块边界调整:

回归证据:
- 安装与升级:
- 冷启动:
- 多设备资源:
- 离线场景:
- 视觉与字体:

报告中的数字应来自保留的产物与脚本输出,而不是手工估算。内容从 HAR 移到 HSP 时,要同时填写所有相关包;图片改为按需下载时,还要记录首用等待、失败兜底和离线能力变化。

如果总 APP 体积几乎不变,但单个 HAP 显著变小,应如实说明这是模块重分布,不应写成整体节省。反过来,包体积下降但冷启动或首屏网络成本明显增加,也需要由产品与工程共同判断是否值得。

总结

HAP 包体积治理不是一次清理活动,而是产物审计能力。先固定 Release 比较口径,再用官方解包能力获得文件清单;从资源、原生库、代码依赖和模块组织四个方向定位大项,用哈希证明重复,用构建结果证明收益。HAR 与 HSP 的取舍也应建立在完整 APP 的重复量和运行回归上。把基线、预算和拆包明细纳入每次发布,体积增长才能从“到上架前才发现”变成日常可控制的工程指标。

参考资料

解包工具文档用于获取可审计产物,模块化设计与 HAR/HSP 文档用于判断共享内容的组织方式,术语文档用于统一 APP、HAP、HAR 与 HSP 的含义。具体构建配置应以项目当前 SDK、模块类型和官方最新说明为准。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐