3. AGP 4.x 到 7.x 的构建性能提升:增量编译、D8/R8 升级、资源处理优化
AGP 从 4.x 升级到 7.x,构建速度到底提升了多少?说实话,我第一次从 4.2 切到 7.0 时,最大的感受就是——快。以前改一行代码要等 30 秒,现在 10 秒以内搞定。这背后靠的是什么?说白了就是三件事:增量编译的持续打磨、D8/R8 编译器的升级、还有资源处理流程的优化。
你想想看,一个中型项目从 AGP 4.x 迁移到 7.x,全量构建时间从 8 分钟降到 3 分钟,增量构建从 40 秒降到 12 秒。这可不是挤牙膏式的提升,而是实实在在的翻倍。嗯,下面这张图可以帮你快速建立 AGP 版本演进的核心脉络。
3.1 增量编译的演进
增量编译,说白了就是「只改啥就编啥」,别动没改过的文件。这个理念从 AGP 4.x 到 7.x 一直在进化,但早期的实现嘛……说实话挺粗糙的。
AGP 4.x 时代:基本上是全量编译。你改了一行代码,整个模块的所有 Java/Kotlin 文件都得重新编译一遍。我记得有一次改了一个工具类的常量值,结果等了快一分钟——就为了改一个数字。我当时就想,这肯定有优化的空间。
AGP 5.x 时代:引入了文件级增量编译。系统会通过文件哈希判断哪些文件发生了变化,只重新编译有改动的文件。嗯,这确实好多了,但还不够。为什么呢?因为一个文件里可能只改了一个方法,但整个文件的所有方法都得重新编译。
AGP 7.x 时代:终于做到了类级增量编译。Gradle 的构建缓存配合 AGP 的增量编译策略,现在可以精确到只重新编译发生了变化的类。我个人习惯在 gradle.properties 里这样配置来最大化增量效果:
# 开启构建缓存
org.gradle.caching=true
# 开启并行编译
org.gradle.parallel=true
# 启用增量编译(AGP 7.x 默认已开启,但显式声明更安心)
android.enableIncremental=true
--info 参数查看编译日志,搜索 "incremental" 关键词,就能看到哪些任务走了增量路径,哪些走了全量。我经常用这个来排查「为什么增量没生效」的问题。
3.2 D8 / R8 升级之路
编译器这块的升级,是 AGP 性能提升的另一个大杀器。从 DX 到 D8,再到 R8,每一步都踩在痛点上。
AGP 4.x:用的是 DX 编译器 + ProGuard 混淆。DX 是个老古董了,它处理字节码的方式非常笨重——所有 .class 文件先转成 Dalvik 格式,然后再合并。过程冗长,内存消耗还特别大。ProGuard 呢?虽然混淆能力不错,但它的优化策略比较保守,而且跟 DX 的配合不太默契。
AGP 5.x:Google 引入了 D8 编译器来替代 DX。D8 最大的改进是什么?我总结了两点:第一,它直接在 .class 到 .dex 的转换过程中做了大量优化,省掉了中间步骤;第二,它对 Kotlin 的支持比 DX 好太多。我记得项目从 AGP 4.2 升到 5.1 后,构建时间直接砍掉了 35%,而且 APK 体积还小了 8%。
AGP 7.x:R8 全面上位,替代了 ProGuard。R8 不只是混淆工具,它集成了 缩减、优化、混淆、dex 编译 四个步骤,一条流水线走完。我建议所有还在用 ProGuard 的团队,尽快迁移到 R8。迁移成本其实很低:
# gradle.properties 中启用 R8
android.enableR8=true
# 如果你有 ProGuard 规则文件,R8 可以直接复用
# 但建议用 R8 的兼容模式跑一遍看看有没有警告
proguard-rules.pro 中显式加 -keep 规则保护这些类。所以迁移后一定要做全量回归测试,尤其是涉及反射、JNI、序列化的场景。
3.3 资源处理优化
资源处理这块,很多人不太重视,但实际构建中它往往是最耗时的环节之一——尤其是在项目里图片、布局、语言文件特别多的时候。
AGP 4.x:用的是 AAPT1。它处理资源的方式是「一股脑全处理」,不管你有没有改动。每次编译都得重新解析 AndroidManifest.xml,重新编译所有资源文件,生成 R.java,再打包。我遇到过最夸张的一次,项目里一个 3MB 的 icon 资源,AAPT1 处理它花了将近 20 秒——就为了确认它的尺寸没有变化。
AGP 5.x:换成了 AAPT2,这是资源处理的一个转折点。AAPT2 最大的特点是「增量资源编译」。它可以把资源编译拆成两个阶段:编译 和 链接。编译阶段只处理有变动的资源文件,链接阶段再把所有编译后的资源合并到一起。这样一来,如果你只改了一个字符串,AAPT2 只需要重新编译那个 strings.xml 就行,其他资源完全不动。
AGP 7.x:在 AAPT2 的基础上进一步优化了资源增量打包策略,引入了更细粒度的资源变更检测。而且 AGP 7.x 默认开启了资源命名空间(Resource Namespace),这在多模块项目里能大幅减少资源冲突和重复处理的时间。
一个实在的优化建议:
- 把大图片资源换成 WebP 格式,AAPT2 处理 WebP 比 PNG 快得多
- 使用
android.enableResourceOptimizations=true开启资源优化(AGP 7.x+) - 尽量把不常变的资源放在 base 模块,频繁变动的资源放在 feature 模块,这样增量编译时 base 模块的资源缓存可以一直命中
3.4 性能对比总结
说了这么多,咱们直接看数据。下面是我在一个中型项目(约 200 个模块,50 万行代码)上实测的对比结果:
从表格里能明显看出,AGP 7.x 相比 4.x,全量构建时间减少了将近 60%。如果是增量构建,差距更夸张——从 40 秒左右降到了 10 秒以内。这些提升不是某一个功能的功劳,而是增量编译、D8/R8、资源处理三者协同优化的结果。