ProGuard 基础规则:混淆、优化、预校验
初学 ProGuard 时,我看到它那一大堆规则头都大了。后来才明白,它的核心其实就三件事:混淆、优化、预校验。说白了,就是把你的代码“改头换面”,再“减减肥”,最后“检查一下身体”,让它跑得更快、更难被逆向。
我个人习惯把这几个步骤串成一条流水线。今天我们就把它拆开,一个个看明白。
核心记忆点:ProGuard 的三大基础规则对应三个处理阶段——-obfuscate(混淆)、-optimize(优化)、-preverify(预校验)。默认这三项都是开启的,你可以在配置里用 -dontobfuscate、-dontoptimize、-dontpreverify 关掉它们。
第一步:混淆
混淆是大多数人最先接触的功能。它会重命名你的类、字段和方法,把 UserManager 变成 a,getUserName() 变成 b()。这么做的目的很直白——让反编译者看不懂你的代码。
嗯,这里要注意:混淆不是万能的。它只重命名,不改变逻辑。我在一个项目中遇到过,业务方把混淆后的堆栈直接扔给我,让我查崩溃原因。我一看全是一堆 a.a.b,根本没法定位。那以后我就养成了习惯——保存 mapping 文件。
我的小技巧:发布前把 mapping.txt 自动归档到 CI 构建产物中,命名带上版本号和构建时间。这样线上出问题,一秒就能反解堆栈。
混淆的常用配置有三类:
- 保留规则:用
-keep告诉 ProGuard 哪些类、方法不要动。 - 不混淆特定类:比如
-keep class com.example.MyClass { *; }。 - 适配第三方库:第三方 SDK 通常会在文档里给你一段 keep 规则,直接复制就行。
为什么需要 keep 规则?因为很多反射调用、JNI、序列化等场景,类名和方法名是硬编码的。一混淆,运行时找不到对应的类,直接 crash。我曾经吃过这个亏——一个网络请求框架用了反射,我没配 keep,结果接口全挂了,差点被测试打死。
第二步:优化
优化阶段比混淆更“激进”。它会内联短方法、移除无用分支、合并代码块,甚至把一些常量直接展开成值。说白了,就是编译器级别的代码微调,让你的程序跑起来更省指令、更省内存。
举个例子:
public int add(int a, int b) {
return a + b;
}
// 调用处
int result = add(3, 5);
经过优化,可能直接变成:
int result = 8;
你想想看,如果你的代码里有一堆这样的小工具方法,优化后能省多少调用栈?尤其是 getter、setter,优化后直接消失。
但优化也有坑。我最怕的就是 循环优化 导致逻辑错误。以前我遇到过一个 Bug:代码里有一个 for 循环,循环体内有个空方法调用。ProGuard 优化时认为那个方法没副作用,直接把循环体干掉了,结果循环里的副作用逻辑(比如更新计数器)也跟着没了。从那以后,我对优化总是格外谨慎。
注意:优化默认是开启的,如果你怀疑优化导致运行时问题,可以先关闭:-dontoptimize。然后逐步开启个别优化项来定位。
第三步:预校验
预校验是很多人容易忽略的一步。它专门为 Android 4.0 (API 14) 及以上版本的虚拟机设计。目标是把字节码中需要运行时校验的部分提前完成,把校验结果直接写进 .class 文件里。这样 App 启动时,虚拟机就不需要再执行一遍校验了。
效果就是:启动速度更快。尤其在 Dalvik 虚拟机上,预校验能节省毫秒级的加载时间。虽然单个类不多,但成百上千个类累积起来,效果就很可观了。
ProGuard 默认就会做预校验,你不需要额外配置。但要注意一点:如果你是做 Java 标准库 或非 Android 项目的混淆,预校验可能会与目标环境冲突。我建议只在 Android 项目里保留它。
什么时候关掉预校验?
- 当你的项目需要兼容 Android 2.x 以下版本时(几乎没有了)
- 当你用 R8 替代 ProGuard 时(R8 内部已经处理了预校验)
- 当你做混淆后单元测试发现 ClassLoader 报错,尝试关闭预校验
三者的配合
现在你应该明白了:混淆管“隐私”,优化管“性能”,预校验管“加载速度”。它们不是孤立的,而是一条流水线。
我记得有一次给一个大型项目做性能优化,只开了混淆,App 启动时间从 2.1 秒降到 1.8 秒。后来把优化也打开,降到 1.5 秒。再打开预校验,又降了 0.1 秒。虽然每步看起来不多,但加起来就是 30% 的提升。
当然,也要做好风险控制。我的习惯是:
- 先在 debug 包上只开混淆,跑通全量测试。
- 再在 release 包上全开,继续跑测试。
- 如果出现问题,先关优化,确认是不是优化引起的。
- 仍然不行,关预校验。
| 规则 | 作用 | 风险 | 建议 |
|---|---|---|---|
-obfuscate |
重命名类、方法、字段 | 反射、JNI、序列化崩溃 | 必开,配好 keep 规则 |
-optimize |
内联、剪枝、常量折叠 | 循环、反射相关逻辑异常 | 默认开,出问题时关 |
-preverify |
提前完成字节码校验 | 兼容旧版虚拟机 | Android 项目开,Java 项目可关 |
最后的建议:不要直接拿别人的配置模板就往项目里塞。每种规则都应该理解后再加入。我踩过的坑可以帮你省下很多调试时间。