3. 保留规则基础语法:核心指令与通配符
ProGuard 与 R8 的保留规则决定了在混淆、压缩、优化过程中哪些代码必须被保留完整名称、结构或实现。正确使用 -keep、-keepclassmembers、-keepnames、-dontwarn 等指令,是避免运行时类/方法找不到、字段访问失败等问题的关键。本章逐一解析每条指令的语义、适用场景,并通过通配符组合实现精确控制。
3.1 核心指令速查
| 指令 | 作用对象 | 保留范围 | 典型用途 |
|---|---|---|---|
-keep | 类和成员 | 类名 + 指定的成员(不混淆、不删除) | 反射入口、JNI 方法、序列化类 |
-keepclassmembers | 仅成员 | 类名不变,但成员保留(类可被重命名) | 保留接口回调方法、构造器 |
-keepnames | 类和成员 | 仅保留名称(可删除未使用的成员) | 保持 API 签名兼容性 |
-dontwarn | 警告抑制 | 不输出特定警告 | 忽略第三方库中缺失引用导致的警告 |
3.2 指令语义详解
-keep:最强保留
同时保留类名和指定成员(方法、字段)。若未指定成员修饰符,默认保留所有成员。
-keep class com.example.MyClass { *; }
-keep class com.example.MyClass {
public <init>(...);
public String getName();
}
-keep class com.example.** { *; }
-keepclassmembers:保留成员,类名可混淆
类可以被重命名,但指定的成员必须保留签名。常用于保留注解处理器生成的方法或 ViewBinding 字段。
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
-keepnames:仅保留名称,未用成员可被移除
类名和成员名不混淆,但若未通过其他规则显式保留,则可能被压缩(删除)。适用于只关心签名兼容性不关心实现的场景。
-keepnames class com.example.api.**
-dontwarn:抑制编译期警告
当第三方库引用了不存在的类或方法时,ProGuard/R8 会发出警告。使用此指令可屏蔽已知无害警告,避免构建中断。
-dontwarn com.thirdparty.library.**
3.3 通配符与匹配模式
通配符用于定义范围,常见模式如下:
| 通配符 | 含义 | 示例 |
|---|---|---|
* | 匹配任意类名(不含点号),或匹配任意字段、方法 | com.example.* |
** | 匹配任意包名及子包(含点号) | com.example.** |
? | 匹配单个字符 | com.example.M?Class |
*** | 匹配任意参数类型(方法签名中) | public void *(***) |
... | 匹配任意数量任意类型参数(方法签名中) | void method(...) |
<init> | 匹配构造器 | <init>(int, ...) |
<fields> | 匹配所有字段 | -keepclassmembers class * { <fields>; } |
<methods> | 匹配所有方法 | -keep class * { <methods>; } |
3.4 指令选择策略
- 反射调用:必须用
-keep保类名及成员,否则运行时报ClassNotFoundException或NoSuchMethodException。 - DataBinding / ViewBinding:用
-keepclassmembers保留绑定字段或方法,类名可以混淆(不影响绑定引用)。 - Gson / Moshi 序列化:使用
-keep保留无参构造器和字段;或结合@SerializedName注解独立保留。 - AIDL / Parcelable:必须
-keep所有实现类及CREATOR字段。 - 第三方库警告抑制:精准使用
-dontwarn匹配缺失的引用包路径,避免误屏蔽真正的问题。
3.5 指令作用域示意图
SVG 释义:下方流程图展示了
-keep(全保留)、-keepclassmembers(仅成员保留)、-keepnames(仅重命名保护)在类级别与成员级别上的覆盖差异。3.6 常见错误与调试
- 错误:过度保留 —— 使用
-keep保留整个包路径**导致 APK 膨胀。对策:缩小到确需反射的类,优先用-keepclassmembers或-keepnames。 - 错误:忽略警告导致构建失败 —— 未添加
-dontwarn,但第三方库引用了已删除的依赖。对策:在构建日志中定位警告路径,针对性添加。 - 调试技巧:运行
gradle app:minifyRelease --stacktrace,查看混淆映射文件mapping.txt确认类/成员是否被重命名;或临时注释混淆开关,对比行为差异。
3.7 性能与稳定性优化
- 减少保留范围:能用
-keepnames时不用-keep,避免阻止 R8 的类合并和内联优化。 - 精准匹配:优先使用类全限定名 + 字段/方法签名,而非通配符
*,让 R8 保留更少冗余信息。 - 合并警告抑制:多个
-dontwarn可合并为一行-dontwarn com.old.library.**, com.new.library.**减少规则数量。 - R8 全模式:若使用
-keep保留整个类,考虑加上allowshrinking修饰符,允许 R8 删除未使用的方法,只保留被引用的部分。
注意:
-keep 类声明中的 { *; } 会保留所有成员,包括私有字段。若该类仅用于反射部分方法,应手动列出签名,避免暴露内部实现。3.8 完整配置示例
# 保留所有 Activity(反射 StartActivity)
-keep class * extends android.app.Activity { *; }
# 保留 Gson 使用的数据类(无参构造器 + 字段)
-keep class com.example.data.** { <init>(); }
-keepclassmembers class com.example.data.** { *; }
# 保留枚举 values() 和 valueOf() 防止混淆
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# 忽略 OkHttp 相关警告(若使用较老版本)
-dontwarn okhttp3.internal.**
-dontwarn okio.Okio
实战建议: 在项目的
proguard-rules.pro 中按功能模块分行写入规则,并添加注释说明每段规则保留的原因。长期维护时,定期运行 mapping.txt 差异对比,清理过时规则。