解决ProGuard混淆代码时出现的java.lang.NoSuchFieldException: xxx…异常


用Android Studio写小Demo的时候,由于需要混淆代码,所以把build.gradle文件里的minifyEnabled false改成了true。

然后编译成release发布版本,就会发现apk包小了不少,说明混淆成功。
这对一般的小项目倒是没什么影响,混淆后程序运行仍然正常,但如果用到了Java的反射机制,就没那么轻松了。

下面是我程序中的一段代码:

Class<RecyclerView> recyclerViewClass = RecyclerView.class;
try {
    Field declaredField = recyclerViewClass.getDeclaredField("mRecycler");
    declaredField.setAccessible(true);
    Method declaredMethod = Class.forName(RecyclerView.Recycler.class.getName()).getDeclaredMethod("clear");
    declaredMethod.setAccessible(true);
    declaredMethod.invoke(declaredField.get(activeRecyclerView));
    RecyclerView.RecycledViewPool recycledViewPool = activeRecyclerView.getRecycledViewPool();
    recycledViewPool.clear();
} catch (Exception e) {
    e.printStackTrace();
}

在Build时就会报类似下面的错:

java.lang.NoSuchFieldException: No field mRecycler in class
Landroid/support/v7/widget/RecyclerView; (declaration of
‘android.support.v7.widget.RecyclerView’ appears in
/data/app/com.xxx……

还有:

java.lang.NoSuchMethodException: clear []

报错提示找不到相应的域和方法。

看最开始的代码我们知道用到了反射,大致的原理是根据字符串去寻找方法,然而代码经过ProGuard混淆后一些方法名都变成了无意义的短字符了(比如a、b、c这种),
但是经过反编译可以发现,代码中的字符串是不会被混淆修改的。
所以程序运行时就无法映射到相应的方法了。

解决办法:
ProGuard官方文档也是建议了大家不要混淆反射所涉及的类方法,因此我们需要自定义配置项目中的proguard-rules.pro文件。
第一个报错解决办法就是保留RecyclerView类不被混淆即可,文件中添加:

-keep class android.support.v7.widget.RecyclerView {*;}

第二个报错,阅读了很多文档,我做了很多尝试,由于技艺不精,还是无法通过配置rules文件来消除混淆对clear()方法的影响,反编译代码后我发现clear()方法名还是被混淆改成了a()。
最后我就干脆把最开始原始代码里的字符串“clear”改成了“a”,然后成功消除报错。

其实这不是很好的解决办法,因为你需要得知相应的方法名被混淆成了什么,而且修改项目源代码去应付混淆也非上策,如果有大神知道如何配置rules文件来保证反射的正常运行,欢迎评论留言,交流指点。
(这里可以通过查看项目下面的 \build\outputs\mapping\release\mapping.txt 映射文件去查看相应的方法名被混淆成了什么,或者简单粗暴一点的,直接查看反编译apk后的代码也能得知)