本节目标
- 理解方舟编译器 2.0 的 AOT 预编译、分层 JIT 与字节码优化机制,掌握编译策略对包体积与启动性能的影响
- 掌握 TreeShaking 的工作原理与构建配置,能够通过死代码消除实现代码体积精简
- 掌握包体积分析工具链(Build Analyzer、扫描工具、AppAnalyzer)的使用,能够精准定位体积热点
- 掌握资源压缩与精简的核心手段:WebP 转换、资源收缩、密度限定词裁剪、字体子集化
- 掌握 so 库优化方法:
strip去符号、compressNativeLibs压缩打包、ABI 精简 - 掌握 HSP 动态共享包在多包场景下消除代码与资源重复拷贝的机制,理解其与 HAR 的核心区别
- 掌握 ArkGuard 字节码混淆的配置方法,能够在安全性与兼容性之间取得平衡
- 掌握依赖冲突解决(
override/resolve_conflict)与按需加载模块的拆分策略 - 能够为应用建立"分析定位 → 代码精简 → 资源瘦身 → so 库优化 → 架构级共享 → 混淆配置"的包体积治理闭环
一、方舟编译器 2.0 与编译优化
1.1 编译策略的核心变化
方舟编译器 2.0 通过 AOT 预编译 + 运行时 JIT + 字节码优化三管齐下,让 ArkTS 应用的启动速度提升 30% 以上,运行时性能逼近原生 C/C++。
相比 1.0,2.0 在三个层面做了关键升级。AOT 预编译增强 :1.0 的 AOT 比较保守,很多优化不敢做(怕类型推断出错);2.0 的类型推断更激进,能内联的函数更多,能消除的冗余更多。分层 JIT :1.0 的 JIT 是"全量编译",2.0 改成了分层------先快速编译跑起来,再根据运行时 Profile 对热点代码做深度优化。字节码优化:2.0 重新设计了字节码指令集,指令更紧凑,解释执行更快。
1.2 编译链路与包体积的关系
ArkTS 编译器(方舟编译器)在构建过程中自动执行以下优化链路:源代码 → 词法分析 → 语法分析 → 语义分析 → TreeShaking → DCE(死代码消除)→ 字节码生成。编译模式的选择直接影响最终产物:AOT 预编译生成机器码,字节码解释执行则生成更紧凑的 .abc 文件。Release 构建开启的 TreeShaking 和 DCE 会显著减小 .abc 体积。
1.3 对开发者的实践意义
你不需要懂编译器的实现细节,但需要知道 2.0 做了什么优化,以及怎么写代码才能让编译器帮你跑得更快。实际开发中,Release 构建务必开启完整的编译优化链路,避免因调试模式打包导致包体积虚高。
二、TreeShaking 与代码精简
2.1 TreeShaking 的工作原理
TreeShaking(摇树优化)在编译构建过程中,静态分析代码的依赖关系,移除那些在代码中没有被引用(未被"摇晃到")的类、函数、变量。它的名字很形象------摇动一棵树,枯叶(无用代码)就会掉落,只留下健康的叶子(有用代码)。
TreeShaking 的工作流程:编译器解析入口 → 构建依赖图 → 标记阶段(从入口开始遍历,可达的标记为 Live Code,不可达的标记为 Dead Code)→ 摇树阶段(保留 Live Code,剔除 Dead Code)→ 生成优化后的字节码。
2.2 TreeShaking 与 DCE 的区别
两者是不同层次的优化:TreeShaking 在模块级别工作,分析整个模块是否被引用;DCE(死代码消除) 在语句/表达式级别工作,分析单条语句是否可执行。TreeShaking 的粒度更粗,DCE 的粒度更细,两者在编译链路中先后执行,共同完成代码精简。
2.3 构建配置与开启方式
TreeShaking 在 Release 构建中默认开启,但需要确保构建模式正确配置。在 build-profile.json5 的 targets 中确认 buildOption 未关闭相关优化。手动判断"这段代码有没有被用到"几乎不可能做到准确------一个工具函数可能被间接引用,一个看似无用的类可能通过反射被加载。TreeShaking 由编译器自动完成,比人工判断更精确。
2.4 反射场景的保留配置
当代码使用反射时,被反射引用的类和方法无法被 TreeShaking 识别为"可达",可能被错误剔除。此时需要在混淆配置文件中通过 -keep 规则显式保留。ArkGuard 提供了 -keep-property-name(保留属性名称)、-keep-global-name(保留顶层作用域或导入导出名称)、-keep-file-name(保留文件路径及名称)等选项。
三、包体积分析工具链
3.1 HAP 包结构剖析
一个典型的 HAP 包本质上是 ZIP 压缩文件,解压后包含:entry.json(模块配置)、resources/(资源文件目录)、ets/(ArkTS 编译产物,含 modules.abc 字节码和 sourceMaps.map)、libs/(原生库目录,含各 ABI 架构的 so 文件)、resources.index(资源索引文件)。
从体积占比来看,代码部分约占 20%-35%,资源部分约占 40%-60%,原生库部分约占 10%-30%,配置与索引约占 1%-5%。资源通常是体积大户,so 库次之,代码部分可通过 TreeShaking 和混淆进一步压缩。
3.2 Build Analyzer
DevEco Studio 内置的 Build Analyzer 在构建完成后分析 .hap 文件,以图形化方式展示各个模块、依赖和资源文件所占的体积大小。它的作用是宏观定位------快速识别出是哪个 HAP、哪个依赖库或哪类资源贡献了最大的体积。
3.3 扫描工具
扫描工具可用于分析检测应用包,根据不同的参数设定,扫描指定路径的 App、HAP、HSP 包内容并输出检测结果报告,为开发者优化包结构或排查问题提供数据支撑。扫描结果重点关注三类文件:重复文件 (同一包内的重复资源,或多包间的重复资源)、较大文件 (确认是否必需,图片可压缩)、特定类型文件(so 文件通过压缩选项优化)。
3.4 AppAnalyzer
AppAnalyzer 是 DevEco Studio 提供的体检工具,支持规则体检和上架前体检。通过菜单栏 Tools > AppAnalyzer 打开,可选择自动或手动方式,模块选择框选择 HarmonyOS 应用/元服务工程模块。它涵盖兼容性、性能、功耗等多种测试类型,帮助开发者在发布前系统性地发现包体积问题。
四、资源瘦身
4.1 图片格式优化
图片资源通常占据 HAP 包体积的 40%-60%,是优化的首要目标。WebP 格式 比 PNG/JPG 压缩率高约 30%,且支持透明通道,应优先将 PNG/JPG 转换为 WebP。SVG 矢量图适合图标和简单图形,体积极小且无损缩放。对大图进行裁剪或压缩,避免在包内携带超出显示需求的原始尺寸图片。
4.2 资源收缩与精简
在 build-profile.json5 中配置资源收缩选项,移除未引用资源。移除 resources 下从未引用的图片、字符串、动画、布局文件。通过 DevEco Studio 的 Build Analyzer 或扫描工具定位"零引用"资源。对于公共资源,只放一处;仅某功能使用的资源不要放主包。
4.3 密度限定词裁剪
如果资源统一存放在 base 目录,可以移除 ldpi~xxxhdpi 全套密度目录。系统会向上就近匹配屏幕密度,不存在时回退到 base。这可以显著减少多套密度图片的重复体积。
4.4 字体子集化
如果引入了自定义字体,只保留 woff2 格式,删除 ttf/otf/eot 等其他格式。若只用到了部分字符(如数字、字母),可使用字体子集化工具,仅提取实际使用的字形,大幅减小字体文件体积。
4.5 资源混淆
ArkGuard 字节码混淆工具对资源文件名进行短名称混淆,并对重复资源进行去重。资源混淆后,资源标识符被替换为短名称,进一步压缩资源索引体积。
五、so 库优化
5.1 strip 去除调试信息
通过配置 "strip": true 来去除 so 文件中的调试信息和符号表,减小 so 体积。该配置需要配置在 HAP 和 HSP 模块中,release 和 debug 模式下都可以配置。
json5
// build-profile.json5
"nativeLib": {
"debugSymbol": {
"strip": true, // 执行 strip,去除调试信息和符号表
"exclude": [] // strip 过滤正则表达式规则
}
}
5.2 compressNativeLibs 压缩打包
DevEco Studio 默认在打包应用时不压缩 so 库文件。配置 so 压缩选项后,DevEco Studio 会将 so 库文件压缩并打包到应用中,从而减小应用包的大小。
json5
// module.json5
{
"module": {
"compressNativeLibs": true // true 表示压缩 so 库
}
}
以 C++ 默认库文件为例,压缩率可达 34%:armeabi-v7a/libc++_shared.so 从 1108k 压缩至 386k。
5.3 ABI 精简
如果应用不需要支持所有 CPU 架构,可以在构建配置中指定目标 ABI,移除不必要的架构目录。对于含 NAPI 的工程,只保留目标 ABI,并用打包工具扫描重复的 so。
5.4 验证方式
优化 so 库后,通过扫描工具确认各 ABI 目录下的 so 文件体积变化。注意 compressNativeLibs 仅控制打包方式(压缩或不压缩),不会自动把任意目录的 so 打进安装包,so 文件仍需正确放置在 libs/ 目录下。
六、HSP 动态共享包与架构级共享
6.1 HAR 的重复拷贝问题
HAR 是静态共享包,编译时被整体打包进每个引用它的 HAP 或 HSP 中。当多个模块引用同一个 HAR 时,代码和资源会在各模块中生成独立副本。HAR 静态库被多个 HSP 间接依赖时,会打出多份副本导致包体膨胀。
6.2 HSP 的共享机制
HSP 是动态共享包,在运行时复用,进程内只存一份。在应用存在多包(HAP、HSP)的场景中,可以使用 HSP 动态共享包在多个包之间共享代码和资源,消除 HAR 静态共享包导致的代码和资源重复拷贝,从而减小应用包大小。
6.3 创建与引用 HSP
在 DevEco Studio 中创建 HSP 模块:选择 File > New > Module,模板类型选择 Shared Library,点击 Next,配置模块信息后点击 Finish。使用方在 oh-package.json5 中配置依赖后引用。
6.4 与 HAR 的选型边界
HAR 适用于:通用的工具函数、UI 组件库、资源文件等不需要运行时共享单例的场景,可以上传至 ohpm 仓库供其他开发者使用。
HSP 适用于:需要跨 HAP 共享单例对象的场景(HAR 会导致多实例问题);需要按需动态下载的功能模块(将非核心功能编译为 HSP,首次安装不包含在主包中);多安装包需要资源共享的场景。
注意事项:大量使用 HSP 替代 HAR,会在编译引用这些 HSP 共享包的模块时触发更多的语言编译任务,导致编译耗时、编译内存占用增加,需要综合评估。
6.5 元服务分包实践
元服务主包有 2MB 上限,可以将非首屏 Tab 的业务模块拆到 HSP,并从 entry 模块的 oh-package.json5 的 dependencies 中移除对 HSP 的静态依赖,使 HSP 体积不计入首包。跨分包跳转使用 NavPushPathHelper.pushPathByName 跳转到 HSP 中的 NavDestination,由系统托管 HSP 的下载与跳转流程。
七、依赖冲突解决与按需加载
7.1 依赖冲突导致的重复编译
对于 ohpm 1.5.0 之前的版本,如果 HAP 依赖了不同版本的 HAR(例如 V1 版本的 harC 和 V2 版本的 harC),默认情况下 V1 和 V2 两个版本的 harC 都会被打包到 HAP 中。这会导致显著的体积膨胀。
7.2 override 机制
使用 ohpm 的 override 机制强制统一依赖版本,减少重复编译问题。在 oh-package.json5 中配置 overrides 字段,指定统一使用的版本号。
7.3 resolve_conflict 配置
开启 resolve_conflict 解决依赖冲突,减少依赖包导致的重复编译问题。在 .ohpmrc 中配置相关选项。
7.4 按需加载模块
将不常用的功能作为按需加载的模块。通过 HSP 分包实现:启动必需代码和真正公共资源留主包;二级功能页、其专属图片/字体/动画移入分包。运行的时候按需下载或者后台提前预加载。
八、ArkGuard 字节码混淆
8.1 混淆的作用与原理
ArkGuard 是字节码混淆工具,通过重命名标识符、打乱控制流、加密字符串来保护核心逻辑,同时也能减小代码体积。混淆在 Release 构建中执行,增加逆向工程难度,保护知识产权。
8.2 基础混淆选项
推荐开发者在默认混淆的基础上,在混淆配置文件中开启以下基础混淆选项:
-enable-toplevel-obfuscation:顶层作用域名称混淆-enable-property-obfuscation:属性名称混淆-enable-filename-obfuscation:文件名称混淆-remove-comments:移除注释
8.3 保留选项与白名单
混淆规则写不好,线上直接崩。需要使用 -keep-property-name 来保留指定的属性名称,使用 -keep-global-name 保留指定的导出/导入名称,使用 -keep-file-name 保留文件路径及名称。
DevEco Studio 提供了混淆助手工具(ObfuscationHelper),可以根据模块和场景对源码进行扫描,快速识别需要配置的保留选项和白名单字段,一键生成白名单混淆规则文件。
8.4 混淆与 TreeShaking 的协同
混淆在 TreeShaking 和 DCE 之后执行。TreeShaking 剔除不可达代码,DCE 剔除死语句,混淆将剩余代码的标识符重命名为短名称。三者协同工作,实现代码体积的层层压缩。
九、多元化习题
习题 1(判断题)
题目:在 HarmonyOS 中,TreeShaking 和死代码消除(DCE)是同一个优化层次,都在模块级别工作。
答案:错误
解读:TreeShaking 在模块级别工作,分析整个模块是否被引用;DCE 在语句/表达式级别工作,分析单条语句是否可执行。两者是不同层次的优化,在编译链路中先后执行。
习题 2(单选题)
题目 :以下哪个字段配置在 module.json5 中,用于压缩 so 库文件以减小应用包大小?
A. strip
B. compressNativeLibs
C. shrinkResources
D. enableObfuscation
答案:B
解读 :compressNativeLibs 配置在 module.json5 中,设为 true 表示以压缩形式打包 so 库文件。strip 配置在 build-profile.json5 的 nativeLib.debugSymbol 中,用于去除调试信息。shrinkResources 用于资源收缩,enableObfuscation 用于开启混淆。
习题 3(多选题)
题目:关于 HSP 相比 HAR 在包体积优化上的优势,以下说法正确的有(多选):
A. HSP 在运行时复用,进程内只存一份,消除代码和资源的重复拷贝
B. HAR 在多个模块引用时会生成独立副本,导致包体积膨胀
C. HSP 相比 HAR 会减少编译耗时和编译内存占用
D. 元服务可以将非首屏模块拆到 HSP,使 HSP 体积不计入首包
答案:A、B、D
解读:HSP 在运行时复用,进程内只存一份,消除重复拷贝,选项 A 正确。HAR 编译时被整体打包进每个引用它的模块,生成独立副本,选项 B 正确。大量使用 HSP 替代 HAR 会导致编译耗时和编译内存占用增加,选项 C 错误。元服务可以将非首屏 Tab 拆到 HSP,并从 entry 的 dependencies 中移除静态依赖,使 HSP 体积不计入首包 2MB,选项 D 正确。
习题 4(代码填空题)
题目:请补全以下配置,去除 so 文件中的调试信息和符号表。
json5
// build-profile.json5
"nativeLib": {
"debugSymbol": {
"______________": true,
"exclude": []
}
}
答案 :strip
解读 :strip 配置为 true 时,会在 cpp 编译产物 so 上执行 strip 操作,去除调试信息和符号表,减小 so 体积。该配置需要配置在 HAP 和 HSP 模块中。
习题 5(代码改错题)
题目:某开发者使用 ohpm 1.5.0 之前的版本,HAP 依赖了 V1 和 V2 两个版本的同一个 HAR,发现包体积异常增大。请指出问题并给出修正方案。
答案 :问题在于 ohpm 1.5.0 之前的版本默认将依赖的不同版本 HAR 都打包到 HAP 中,导致重复编译。修正方案是使用 override 机制或开启 resolve_conflict 强制统一依赖版本。
json5
// oh-package.json5
{
"overrides": {
"harC": "2.0.0" // 强制统一为 V2 版本
}
}
解读 :依赖冲突会导致多个版本的同一 HAR 被重复打包。通过 override 强制统一版本,或开启 resolve_conflict 自动解决冲突,可以减少依赖包导致的重复编译问题。
习题 6(简答题)
题目:简述 HAP 包的体积组成,以及各部分的优化手段。
答案:HAP 包体积主要由四部分构成:代码部分(ArkTS 字节码和 SourceMap),约占 20%-35%,优化手段包括 TreeShaking 剔除未引用代码、ArkGuard 混淆缩短标识符、Release 构建开启完整优化链路;资源部分(图片、字符串、布局文件等),约占 40%-60%,优化手段包括图片转 WebP/SVG、资源收缩移除未引用资源、裁剪密度限定词目录、字体子集化、资源混淆;原生库部分(各 ABI 的 so 文件),约占 10%-30%,优化手段包括 strip 去除调试信息、compressNativeLibs 压缩打包、ABI 精简;配置与索引部分,约占 1%-5%,优化空间有限。
解读:资源通常是体积大户,so 库次之,代码部分可通过编译优化进一步压缩。优化前应使用 Build Analyzer 和扫描工具定位热点,再针对性地优化。
习题 7(简答题)
题目:简述在多包场景下,如何综合使用 HSP、override 和按需加载来实现包体积优化。
答案 :在多包(HAP、HSP)场景中,HSP 用于在多个包之间共享代码和资源,消除 HAR 导致的重复拷贝,从而减小应用包大小。对于被多个模块间接依赖的公共 HAR,应将其转为公共 HSP,由各业务 HSP 共享,真正消除重复体积。override 机制用于强制统一依赖版本,避免不同版本的同一 HAR 被重复打包。按需加载模块将不常用的功能拆分为独立的 HSP,并从 entry 的 dependencies 中移除静态依赖,使这些模块的体积不计入首包。运行时通过 NavPushPathHelper.pushPathByName 跳转到 HSP 中的页面,由系统托管 HSP 的下载与跳转流程。三者协同使用,可以从架构层面系统性地控制包体积。
解读:包体积优化的最高层次是架构级优化。HSP 解决"重复拷贝",override 解决"版本冲突导致的重复",按需加载解决"首包过大"。三者的共同目标是让代码和资源在正确的位置只存在一份。
十、本节知识点总结
方舟编译器 2.0
通过 AOT 预编译增强、分层 JIT 和字节码优化三管齐下,启动速度提升 30% 以上。编译链路自动执行 TreeShaking 和 DCE,Release 构建务必开启完整优化。
TreeShaking
在模块级别静态分析依赖关系,剔除不可达的类、函数、变量。与 DCE(语句级死代码消除)互补。反射场景需通过 -keep 规则保留相关代码。
包体积分析工具链
Build Analyzer 宏观定位体积热点,扫描工具输出重复文件、较大文件、特定类型文件的分析报告,AppAnalyzer 提供规则体检和上架前体检。优化前先用工具定位,再针对性处理。
资源瘦身
图片转 WebP(压缩率约 30%)或 SVG,配置资源收缩移除未引用资源,裁剪密度限定词目录,字体子集化。资源通常占 HAP 体积 40%-60%,是优化首要目标。
so 库优化
strip: true 去除调试信息和符号表,compressNativeLibs: true 压缩打包(压缩率约 34%),ABI 精简移除不必要架构。
HSP 架构级共享
HSP 运行时复用、单副本,消除 HAR 静态打包导致的重复拷贝。适用于跨 HAP 共享单例、按需加载模块、多安装包资源共享。需评估对编译性能的影响。
依赖冲突解决
override 强制统一版本,resolve_conflict 自动解决冲突,避免多版本 HAR 重复打包。按需加载模块将非核心功能拆分为 HSP,不计入首包。
ArkGuard 混淆
开启顶层作用域、属性、文件名混淆和注释移除。通过 -keep-property-name、-keep-global-name、-keep-file-name 保留必要标识符。使用 ObfuscationHelper 一键生成白名单。
下节预告
第6课将进入 ArkUI 的 NDK 进阶与原生渲染管线的学习,涵盖 Native 侧组件节点的深度操作、自定义绘制管线的搭建、以及 ArkTS 与 C++ 之间高性能数据通道的设计与实现。