ProGuard 配置文件的灵魂:proguard-rules.pro 的语法与核心指令

做 Android 混淆,说白了就是跟 proguard-rules.pro 这个文件打交道。我见过不少新手,把网上搜来的规则一股脑贴进去,结果要么崩得一塌糊涂,要么混淆等于没开。

今天我们就把它彻底拆开。你搞懂了结构、语法、以及那几个最常用的指令,后面再遇到什么诡异的崩溃,心里就有底了。

文件在哪?长什么样?

位置固定:app/proguard-rules.pro。如果你用的是老项目,也可能是 proguard-project.txt,但新项目基本都是 .pro 结尾。

我的习惯:在 module 的 build.gradle 里配好 proguardFiles,把规则独立出来,别跟构建脚本混在一起。

文件本身是纯文本,每行一条规则。空行和 # 开头的注释会被忽略。

# 这是一条注释
-keep class com.example.MyClass { *; }
-dontwarn com.example.**

语法没那么玄乎,记住三样就行

规则长的像这样:

[-指令] [修饰符] [类描述] { [成员描述] }

指令:就是 -keep、-dontwarn 这种,告诉 ProGuard 要干嘛。

修饰符:比如 allowobfuscation、includedescriptorclasses,用来微调行为。

类描述:用通配符匹配类。最常用的通配符就这几个:

通配符含义示例
*匹配当前包中的任何类,不递归子包com.example.* 只匹配一级
**匹配任意包路径(包括子包)com.example.** 匹配所有子包
?匹配单个字符com.example.My?lass
extends匹配某个类的子类class * extends android.app.Activity

成员描述:写在花括号里,比如 <init> 表示构造函数,* 表示所有成员。

我曾经在第三方 SDK 的规则里漏写了 { *; },结果那个类的方法全被混淆了,运行时直接 NoSuchMethodError。记住:花括号里的内容是你要保留的“具体成员”,{ *; } 就是保留全部。

三个必会指令,调了无数次崩才悟透

1. -keep:保命符

-keep 告诉 ProGuard:这个类、这些成员,你别动。不改名、不删、不优化。

典型场景:

  • 反射调用的类或方法
  • JNI 的 native 方法
  • Gson / FastJson 的实体类
  • WebView 的 JavaScriptInterface
  • 被布局文件引用的自定义 View
-keep class com.example.data.** { *; }

# 只保留类,不保留成员(通常不够,小心)
-keep class com.example.api.Response

# 保留类的特定方法
-keep class com.example.security.CryptoUtil {
    public byte[] encrypt(byte[]);
}
我曾经踩过的坑:用 -keep class 保留了一个模型类,但没写 { *; },导致它的字段全被混淆了。Gson 反序列化时字段名对不上,直接返回 null。所以,如果你不确定保留哪些成员,直接写 { *; } 最保险——除非你想故意缩小包体积。

2. -dontwarn:闭嘴令

ProGuard 在扫描引用时,如果找不到某个类,会输出警告。大部分警告无害,尤其在引用不到的可选依赖时。

-dontwarn com.example.optional.**
-dontwarn javax.annotation.**

不加 -dontwarn 的话,警告默认不会导致构建失败。但如果你开启了 -ignorewarnings 或你自己加了 android { defaultConfig { ... proguardFiles ... } } 里的特殊配置,警告可能变成错误。

我个人习惯在引入一个第三方库时,先进去看它的 proguard 建议,然后直接复制它的 -dontwarn 规则。省心。

怎么判断要不要写 -dontwarn?编译时如果看到 Warning: there were X unresolved references to classes ...,而且明确知道这些类在运行时不会出现(比如只在开发期使用),就加上对应的 -dontwarn。否则,能补 -keep 就补 -keep,别无脑屏蔽所有警告。

3. -keepattributes:保留元数据

有些信息在编译后会被 ProGuard 丢掉,比如注解、内部类关系、签名等。但某些框架需要它们才能正常工作。

属性名作用典型依赖方
Signature保留泛型签名Gson / Moshi 反序列化泛型
Exceptions保留方法抛出的异常声明Java 反射检查
InnerClasses保留内部类与外部类的关系某些 IoC 容器
*Annotation*保留所有注解Retrofit / EventBus / Dagger 等
-keepattributes Signature, *Annotation*, InnerClasses, EnclosingMethod

大多数项目可以直接用它。我自己写框架时,还会加上 RuntimeVisibleAnnotations 和 RuntimeVisibleParameterAnnotations,以免注解处理器抽风。

其他常用指令一览

虽然这几条不是本章重点,但我会在后续章节频繁用到,先混个脸熟:

  • -keepclassmembers:只保留类成员,不保留类本身(缩小影响面)
  • -keepnames:保留名称,但允许删除(适用于反射只关心方法名的情况)
  • -optimizationpasses:指定优化轮数,默认 1 轮,调高可能减小体积但也容易崩
  • -allowaccessmodification:允许改变访问权限(public 变成 private 等),慎用

你想想看,-keep 和 -keepclassmembers 的区别是什么?
-keep 保留了整个类及其成员(名字、结构),-keepclassmembers 只保留成员,类本身可以被重命名。
在遇到混淆崩溃时,先试 -keep class ... { *; } 大多数情况能解决问题。如果包体积敏感,再收窄到 -keepclassmembers。

知识体系图谱

下面这张图把 ProGuard 配置文件的结构、指令分类以及处理流程串起来了。你盯着看一分钟,整体框架就能印在脑子里。

proguard-rules.pro 配置文件结构 & 核心指令 proguard-rules.pro 语法基础 核心指令 辅助指令 注释 / 空行 通配符 (* ** ?) 规则格式 -keep -dontwarn -keepattributes -keepclassmembers -keepnames -allowaccessmodification -optimizationpasses 记住三大指令:-keep 保命,-dontwarn 闭嘴,-keepattributes 保元数据 避坑指南(我的血泪经验) 1. -keep 类时务必确认是否保留成员 { *; },否则反射必崩 2. -dontwarn 不要滥用,只针对确实不存在的类,否则可能掩盖致命错误 3. 泛型相关的崩溃,先检查是否加了 -keepattributes Signature 4. 规则写在文件后面会覆盖前面的,注意顺序

小结两句

配置文件的结构说复杂也复杂,说简单也简单:指令、通配符、成员描述三件套。掌握了 -keep、-dontwarn、-keepattributes,你已经能解决 90% 的 ProGuard 崩溃问题了。