ProGuard 配置文件的灵魂:proguard-rules.pro 的语法与核心指令
做 Android 混淆,说白了就是跟 proguard-rules.pro 这个文件打交道。我见过不少新手,把网上搜来的规则一股脑贴进去,结果要么崩得一塌糊涂,要么混淆等于没开。
今天我们就把它彻底拆开。你搞懂了结构、语法、以及那几个最常用的指令,后面再遇到什么诡异的崩溃,心里就有底了。
文件在哪?长什么样?
位置固定:app/proguard-rules.pro。如果你用的是老项目,也可能是 proguard-project.txt,但新项目基本都是 .pro 结尾。
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 规则。省心。
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 配置文件的结构、指令分类以及处理流程串起来了。你盯着看一分钟,整体框架就能印在脑子里。
小结两句
配置文件的结构说复杂也复杂,说简单也简单:指令、通配符、成员描述三件套。掌握了 -keep、-dontwarn、-keepattributes,你已经能解决 90% 的 ProGuard 崩溃问题了。