3. 混淆规则基础

混淆规则说白了就是告诉 R8:「这个别动,那个可以删」。我个人习惯把规则文件比作一份作战命令,你得明确下指令,R8 才知道哪些代码要死守,哪些可以放手。你想想看,如果指令都模棱两可,那混淆结果肯定出岔子。

这一节我们就把 -keep、-dontwarn、-keepclassmembers、-keepattributes 这四个最常用的指令彻底讲透。搞懂了它们,你就掌握了写规则的基本功。

R8 混淆核心指令 -keep 保留类 & 成员 阻止混淆压缩 -dontwarn 忽略编译警告 处理缺失引用 -keepclassmembers 仅保留类成员 不保留类本身 -keepattributes 保留元数据 注解/签名等 提示:四个指令各司其职,组合使用才能覆盖所有混淆场景

3.1 -keep:保留的核心武器

-keep 是规则文件里出镜率最高的指令。它的作用很直接:告诉 R8 某些类、方法或字段不许动——不能改名、不能删除、不能优化。说白了就是给代码上「护身符」。

基本语法长这样:

-keep class com.example.MyClass { *; }

大括号里的 *; 表示保留该类下所有成员。如果只想保一部分,可以写得更精确:

-keep class com.example.MyClass {
    public void methodName();
    public String fieldName;
}

通配符写法也很常用,尤其是某个包下的类都想保:

-keep class com.example.** { *; }
提示: ** 匹配当前包及所有子包,* 只匹配当前包。用哪个取决于你想覆盖多深。

我在项目中遇到过不少因为 -keep 规则没写到位导致的问题。印象最深的一次是反射调用一直报 ClassNotFoundException,查了半天发现是混淆把类名改了。加一行 -keep 就解决了,前后不超过十秒。那之后我就养成了一个习惯——凡是反射、JNI、序列化用到的类,一律上 -keep。

3.2 -dontwarn:安静编译的秘诀

-dontwarn 的作用是让 R8 忽略某些编译警告。有些第三方库在编译时会报「找不到引用」之类的 warning,但实际运行并不会出问题。这时候你就可以用 -dontwarn 让控制台清静点。

写法很简单:

-dontwarn com.example.library.**
警告: 我建议不要滥用 -dontwarn。它就像「眼不见心不烦」,但真正的隐患可能被掩盖。只在你确定这些警告不影响运行时再用。

举个例子,有些老旧的库依赖了已经移除的 API,编译时会报 warning。如果你不确定这些 warning 是否安全,先查一下再决定要不要加 -dontwarn。我曾经因为随手加了一条 -dontwarn,结果线上崩溃了三天才找到根因——一个被忽略的 warning 其实是致命错误。嗯,从那以后我再也不敢随便 -dontwarn 了。

3.3 -keepclassmembers:精准保留类成员

-keepclassmembers 和 -keep 的区别在哪?说白了,-keep 是连类带成员一起保,-keepclassmembers 只保成员,类名该混淆还是混淆。

什么时候用?最常见的场景是回调方法、事件监听、或者某些只靠方法名识别的调用方式。

-keepclassmembers class com.example.MyClass {
    public void onEvent*(int);
    private void callback();
}

我记得有一次排查线上崩溃,发现某个回调方法被混淆成了 a(),调用方拿着原方法名死活找不到。用 -keepclassmembers 把回调方法名保下来就稳了。类名可以变,但方法签名不能丢——这就是这个指令的精髓。

关键区别:
  • -keep → 类名 + 成员都保留
  • -keepclassmembers → 只保留成员,类名可以混淆

3.4 -keepattributes:保留元数据

-keepattributes 用来保留类、方法、字段上的元数据信息,比如注解、签名、异常表等。这些信息在运行时如果被抹掉,某些框架会直接罢工。

常见的写法:

-keepattributes *Annotation*
-keepattributes Signature
-keepattributes Exceptions
-keepattributes InnerClasses

我重点说说 Signature。Java 的泛型信息在编译后会被擦除,但字节码里其实还留了一份签名信息供反射使用。如果你用了 Gson、FastJson 这类序列化框架,泛型信息一丢,反序列化直接崩。

我曾经因为没加 -keepattributes Signature,导致运行时泛型信息全部丢失,List<String> 反序列化出来变成了 List<Object>,强转就挂。加回来之后世界清净了。所以我的规则文件里这三行永远是标配:

-keepattributes *Annotation*
-keepattributes Signature
-keepattributes Exceptions

3.5 指令对比速查

四个指令各有各的用处,选错了要么保得不够,要么保得太多(APK 变大)。下面这个表格帮你快速决策:

指令 保护范围 典型场景 注意事项
-keep 类 + 成员 反射、JNI、序列化 保太多会增大 APK
-dontwarn 无(仅抑制警告) 缺失引用的第三方库 滥用会掩盖真实错误
-keepclassmembers 仅成员 回调、事件监听 类名仍会被混淆
-keepattributes 元数据 注解、泛型、序列化 按需添加,不要一股脑全保
我的建议: 写规则时先想清楚「这段代码为什么不能被混淆」,然后对症下药。能用 -keepclassmembers 就别用 -keep,能加 -dontwarn 但先确认安全再加。精准才是王道。