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(仅重命名保护)在类级别与成员级别上的覆盖差异。
保留指令作用域对比 保留指令作用域对比 -keep 类名 ✓ 成员名 ✓ 成员内容 ✓ -keepclassmembers 类名 ✗ 成员名 ✓ 成员内容 ✓ -keepnames 类名 ✓ 成员名 ✓ 成员内容 ✗ ✓ = 保留/不混淆 ✗ = 可混淆/可删除 通配符可进一步约束作用于哪些类、成员及修饰符 实际配置时多条指令可叠加 例如:-keep 与 -keepclassmembers 配合实现精准保留

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 差异对比,清理过时规则。