ProGuard与R8的关系:Android Gradle插件中的默认工具
说起Android混淆,大家最先想到的肯定是ProGuard。但如果你打开最新版的Android Studio,新建一个项目,看看build.gradle里的配置——你可能找不到ProGuard的影子了。从AGP 3.4.0开始,默认混淆工具已经换成了R8。
这俩到底什么关系?R8是取代ProGuard,还是它的升级版?我个人习惯把R8理解为ProGuard的“亲儿子”——Google从头重写了一套,专门为Android优化。
历史的转折:为什么 Google 要自己搞一套
ProGuard 是个老牌工具,Java 生态用了十几年。它做三件事:压缩、优化、混淆。但 ProGuard 有个硬伤——它针对的是 JVM 的 .class 字节码,不是 Android 的 DEX 格式。每次构建都得先跑 ProGuard 出 .class,再用 D8 转成 .dex,中间多一道开销。
R8 就不一样了。它直接吃 .class 或者 .jar,一口吞掉压缩、优化、混淆、DEX 转换四个活,省去中间流转。我在项目中对比过,同一个中型 App,用 R8 构建时间缩短了 30% 左右。
一句话总结:R8 = ProGuard 的能力 + D8 的速度 + 更激进的优化。
规则文件:ProGuard 的遗产
这也是个好消息:你以前写的 proguard-rules.pro 文件,R8 基本全兼容。什么意思?你写的 -keep class com.example.**、-dontwarn 这些,R8 都认。有些老项目里写了 -dontoptimize、-dontobfuscate,R8 也照单全收。
不过有几条规则会触发兼容模式:
-whyareyoukeeping→ R8 直接支持,输出格式略有不同-adaptclassstrings→ R8 有自己的一套处理方式-repackageclasses ''→ 两者行为基本一致
嗯,这里要注意:R8 对某些规则的执行顺序和 ProGuard 不一样。比如 -keepclassmembers 的优先级,我踩过坑——ProGuard 里保留的字段,到了 R8 被优化掉了。排查半天,发现是 -allowaccessmodification 和 keep 规则冲突。后来我把 keep 规则写得更精确,问题就解决了。
R8 的激进优化:是福是祸
R8 默认比 ProGuard 优化得更狠。它会做:
- 常量传播和内联:直接把小方法体拷贝到调用处,减少虚拟方法调用
- 合并类:把多个逻辑耦合的类塞到同一个 .dex 文件,减少跨 dex 引用
- 类合并:
-merge技术,把只有静态方法的工具类合并到调用类里 - 重复数据消除:识别并移除相同的字符串、类名
听起来很美好,但有时候会翻车。我记得有一次 App 上线后,某个页面崩溃率飙升。调查发现是 R8 把 String.format 里的格式字符串合并炸了——因为两个不同地方的格式化字符串恰好相同,R8 以为可以共享,结果一个 %s 被另一个 %d 覆盖。恢复现场后,我加了一条 -keepclassmembers,allowshrinking class * { *** format(...); } 才搞定。
警告:R8 的类合并功能可能会导致一些极其隐蔽的反射问题。如果你用 Class.forName() 动态加载类,建议加上 -keep class ** { *; } 或者用 @Keep 注解保护。我曾经有个老项目,用 Gson 反序列化,R8 合并类后字段路径变了,直接炸成空指针。
如何判断当前项目用的是 ProGuard 还是 R8
很简单,看 gradle.properties 或者构建日志:
// 在 gradle.properties 中,下面这个开关默认是 true(AGP 3.4+)
android.enableR8 = true
// 如果你看到这句日志,说明走了 ProGuard
// [ProGuard] Printing configuration to file...
// 如果是 R8,日志里会有
// [R8] Running R8 with full mode
如果你用的是 AGP 3.4.0 到 3.5.0,可以通过显式设置来切换:
// 强制使用 ProGuard
android.enableR8 = false
// 回到 R8
android.enableR8 = true
从 AGP 3.6.0 开始,Google 移除了 android.enableR8 开关,R8 成为唯一选项。想切回 ProGuard?除非你降级 AGP 版本。
核心对比表
| 对比维度 | ProGuard | R8 |
|---|---|---|
| 输入格式 | .class 字节码 | .class 或直接处理 AAR |
| 输出流程 | 压缩→优化→混淆→D8转dex | 统一处理,直接出 dex |
| 构建性能 | 较慢(多一步 I/O) | 快 30%~50% |
| 代码优化 | 基础级 | 更激进(类合并、内联) |
| 资源处理 | 不支持 | 支持资源优化(删除无用资源) |
| 规则兼容性 | — | 95% 以上规则直接可用 |
| 默认启用 | AGP 3.3 及更早 | AGP 3.4+ |
迁移时我个人的避坑清单
如果你还在用 ProGuard,想迁移到 R8,我建议按这个步骤走:
- 先把 AGP 升级到 3.4.0 以上。太老的版本不能直接用 R8。
- 保留所有旧的 Keep 规则。先别删,R8 能认。
- 打开 Full Mode。在
gradle.properties加android.enableR8.fullMode=true。这个模式会开启 R8 所有优化,包括类合并。 - 跑一遍 Release 构建。用
./gradlew assembleRelease --stacktrace,看看有没有编译期错误。 - 检查
-whyareyoukeeping的输出。R8 的-whyareyoukeeping会给出更详细的链式原因分析,可以用来验证你的 keep 规则是否过多。 - 用
R8TraceReference工具(在build/outputs/mapping/release/里)检查未被使用的引用。 - 全量回归测试。特别是涉及反射、序列化、Native 调用的地方。
小技巧:如果你在迁移过程中遇到 R8 报错说“无法处理某个类”,可以先在规则文件里加一条 -dontwarn com.example.unused.** 跳过警告。但这不是长久之计——事后一定要去确认那个类是不是真的不需要保留。我曾经因为偷懒加了一堆 -dontwarn,结果上线后某个支付 SDK 因为缺少 class 直接闪退,那周我加了两天班。
一句真心话
我个人强烈建议你直接用 R8。苹果从 iOS 12 开始也用类似的 linker 优化,Android 在 arm64 设备上主流 App 的 dex 大小已经压缩到原来的一半以下。R8 不是 ProGuard 的替代品,它是 Android 构建工具链的进化产物。与其守着老工具,不如早点拥抱新机制。
当然,如果你的项目还在用 AGP 3.2 以下、或者依赖一些极其古老且不兼容 R8 的第三方库,那 ProGuard 暂时还能顶一阵。不过这类项目也该考虑升级了——毕竟 Google 已经明确不再维护 ProGuard 的 Android 集成。