常见混淆错误类型:ClassNotFoundException、MethodNotFoundException、FieldNotFoundException
混淆这玩意儿,说白了就是给代码“改头换面”。类名、方法名、字段名全给你重命名。改得好,包体积瘦身,代码安全;改不好,运行时直接炸给你看。我在项目里遇到过好几次这类错误,每次排查都像在玩“找不同”。嗯,今天就把最常见的三种错误类型掰开了讲。
核心认知:混淆错误本质上是 代码引用 与 混淆后的符号 不匹配导致的。你代码里写死的类名、方法名、字段名,在混淆后没找到对应的新名字,就报错了。
1. ClassNotFoundException
这个错误最容易发生在两类场景:反射 和 动态加载。你写了个 Class.forName("com.example.MyClass"),结果运行时说找不到这个类。混淆后类名被改成了 a.b.c,当然找不到。
我个人习惯,在 ProGuard 配置里把所有用到反射的类都显式 -keep 住。但我一开始也吃过亏,以为加了 -keep class com.example.* 就万事大吉,结果子包下的类还是被混淆了。
避坑指南:-keep class com.example.** 才能递归保持所有子包。我曾经只用了一个星号,排查了一整天才发现。
还有一种情况:你引用了一个 外部库 的类,但该库没被打进 APK。混淆并不会自动帮你加上依赖,所以报错。这时候要检查 build.gradle 的依赖配置,或者手动 -libraryjars 声明库路径。
2. MethodNotFoundException
这个更隐蔽。类还在,方法签名变了。比如你原来有个 getUser(int id),混淆后变成了 a(int),但你的代码里通过反射调用了 getUser 这个名称,就炸了。
我遇到过最头疼的一次:一个第三方 SDK 的接口里定义了一个泛型方法 。混淆后签名因为泛型擦除和重命名变得面目全非。最后只能把整个类 -keepclassmembers 住方法名。
-keepclassmembers class com.some.sdk.JsonParser {
public <T> T fromJson(java.lang.String, java.lang.Class);
}
注意,这里必须写完整的参数类型。如果你漏了泛型信息,ProGuard 可能匹配不到。建议先用 -printseeds 选项生成 seeds.txt,看看哪些成员被保留了。
警告:部分混淆配置会默认移除 @Override 方法上的注解,导致某些框架(如 Retrofit、Gson)的接口方法无法正确映射。你可以加上 -keepattributes Signature,Exceptions,*Annotation* 来保留。
3. FieldNotFoundException
字段错误最常见于 序列化/反序列化 和 JSON 解析。比如你用 Gson 或 Moshi 时,字段名被混淆成了 a,JSON 里的 "name" 字段就赋不进去了。
说白了,这类框架靠字段名做反射赋值。你想想看,如果你不 keep 住字段,Gson 去给 a 赋值,但 JSON 里是 name,对不上号。
-keepclassmembers class com.example.User {
private String name;
private int age;
// 或者更简单:用 @SerializedName 注解后 keep 所有字段
}
我个人的建议是:所有数据模型类(Bean、DTO、POJO)统一用 -keepclassmembers 保留字段名。如果你的字段有 @SerializedName 注解,还可以加上 -keepattributes *Annotation* 来保留注解,然后只 keep 带注解的字段。
对比总结:三种错误虽然表现不同,但根因都是“引用目标被混淆改名”。
| 错误类型 | 典型触发场景 | 排查切入点 | 常用配置 |
|---|---|---|---|
| ClassNotFoundException | 反射、动态加载、外部库缺失 | mapping.txt 看类是否存在 | -keep class ... |
| MethodNotFoundException | 反射调用方法、泛型方法 | seeds.txt 检查方法保留 | -keepclassmembers |
| FieldNotFoundException | JSON 序列化、ORM 映射 | mapping.txt 看字段名 | -keepclassmembers + 注解保留 |
最后补充一个通用排查流程:当你看到任何 NotFoundException 时,第一时间打开 build/outputs/mapping/release/mapping.txt,搜索报错的原始类名、方法名或字段名。如果在 mapping 中能找到,说明被混淆了,你需要补 keep 规则;如果找不到,说明该代码块被 ProGuard 的 shrink(瘦身)阶段整个移除了——你想想看,ProGuard 认为你代码里没用到它,所以直接干掉了。
遇到 shrink 移除的情况,可以用 -whyareyoukeeping 选项查看某个类为什么被保留,反推哪里缺失了引用。这个选项平时不常用,但在疑难杂症时救过我两次。