ProGuard与R8的关系:Android Gradle插件中的默认工具

说起Android混淆,大家最先想到的肯定是ProGuard。但如果你打开最新版的Android Studio,新建一个项目,看看build.gradle里的配置——你可能找不到ProGuard的影子了。从AGP 3.4.0开始,默认混淆工具已经换成了R8。

这俩到底什么关系?R8是取代ProGuard,还是它的升级版?我个人习惯把R8理解为ProGuard的“亲儿子”——Google从头重写了一套,专门为Android优化。

Android 构建流程中的混淆/优化环节 Java/Kotlin 源码 编译 → .class 文件 D8 / R8 编译器 .class → .dex ProGuard (旧版可选) 最终 APK / AAB .dex + 资源 R8 核心 (AGP 3.4+ 默认) 代码压缩 混淆重命名 资源优化 ProGuard 规则文件 (proguard-rules.pro) 兼容性配置 (keep / whyareyoukeeping) 从 ProGuard 迁移至 R8

历史的转折:为什么 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 优化得更狠。它会做:

  1. 常量传播和内联:直接把小方法体拷贝到调用处,减少虚拟方法调用
  2. 合并类:把多个逻辑耦合的类塞到同一个 .dex 文件,减少跨 dex 引用
  3. 类合并:-merge 技术,把只有静态方法的工具类合并到调用类里
  4. 重复数据消除:识别并移除相同的字符串、类名

听起来很美好,但有时候会翻车。我记得有一次 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,我建议按这个步骤走:

  1. 先把 AGP 升级到 3.4.0 以上。太老的版本不能直接用 R8。
  2. 保留所有旧的 Keep 规则。先别删,R8 能认。
  3. 打开 Full Mode。在 gradle.properties 加 android.enableR8.fullMode=true。这个模式会开启 R8 所有优化,包括类合并。
  4. 跑一遍 Release 构建。用 ./gradlew assembleRelease --stacktrace,看看有没有编译期错误。
  5. 检查 -whyareyoukeeping 的输出。R8 的 -whyareyoukeeping 会给出更详细的链式原因分析,可以用来验证你的 keep 规则是否过多。
  6. 用 R8TraceReference 工具(在 build/outputs/mapping/release/ 里)检查未被使用的引用。
  7. 全量回归测试。特别是涉及反射、序列化、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 集成。