crash with NoSuchMethodError after proguard with method references

风流意气都作罢 提交于 2019-12-01 16:04:45

Working through these bugs that -keep doesn't fix is a REAL pain and the only way I've ever made headway on them is by following this strategy:

  1. Figure out at what phase of the proguard cycle the bug is being introduced (shrinking, optimizing or obfuscating)
  2. Add/remove exceptions for that step, working your way from the broadest scope of exclusion to the narrowest until the problem resurfaces

E.G.

Verify that this is an optimization issue or not

  1. Add -dontoptimize instead of the -optimizations string rebuild and test
  2. If the crash is mitigated, work backwards through the classes of optimization exclusions at the highest level !method/*, !code/*, !class/*, !field/* until you determine which exclusion makes the issue go away
  3. Determine what the minimum exclusion is in that class of exclusion (assuming it was !method/*, go from it to !method/marking/* and then if that works too try !method/marking/final. If that works, then you've found the minimum exclusion)

This could very well be a bug in the Proguard rules of one of the libs you're using or in the version of Proguard you're using itself (I've seen both) so try to update both as well.

Most of the time, the website/github of your library provides the necessary proguard rules like retrolamda:

-dontwarn java.lang.invoke.*
-dontwarn **$$Lambda$*

Proguarding is a trail-error story. Check your logging to see which library, class or component causes a problem and add them carefully to the rules :).

Your error NoSuchMethod specifically:

Your code is probably calling something like myClass.getMethod, trying to find some method dynamically. Since ProGuard can't always detect this automatically, you have to keep the missing method in using the appropriate -keep option:

-keepclassmembers class mypackage.MyClass { void myMethod(); }

This happens when a class is looking for, or directly invoking a method of a given argument via reflection in runtime. Proguard can't warn you about this, because there is no link between the obfuscated class and the consumer class in compile time. You can have something like

public class AbstractbaseSomething{

    public abstract void doStuff();

}

public class iDoStuff{

    public void letsGo(Object o){
        Method method = o.getClass().getDeclaredMethod("doStuff");
        // do stuff with the method
    }

}

Since the method is referenced used a string with the name, proguard does not detect it, and in runtime, you get a crash. the only solution is, assuming you can't modify the code, is to avoid obfuscating the method and class.

(You can check a more realistic example in Ormlite-Android)

after re-check carefully, i concluded it probably not a bug of proguard, only gradle.

first, i let the source code using general interface coding style :

mExecutor.exec(getIntent(), new MyInterface() {
    @Override
    public void execute() {
        finish();
    }
});

then i clean the build cache and rebuild :

./gradlew clean
./gradlew :app:assembleRelease

I perform the output release app and make it reach the problematic code, it work without crash.

this time i turn to method references :

mExecutor.exec(getIntent(), this::finish);

but i didn't clean the build cache before re-building :

./gradlew :app:assembleRelease

now re-perform with the crash happen :

05-22 11:35:33.870 D/AndroidRuntime(  631): Shutting down VM
05-22 11:35:37.470 E/AndroidRuntime(  631): FATAL EXCEPTION: main
05-22 11:35:37.470 E/AndroidRuntime(  631): Process: com.cmrobot.assistant, PID: 631
05-22 11:35:37.470 E/AndroidRuntime(  631): java.lang.NoSuchMethodError: com.session.a.finish
05-22 11:35:37.470 E/AndroidRuntime(  631):     at com.newsessions.b.executeDone(Unknown Source)
05-22 11:35:37.470 E/AndroidRuntime(  631):     at com.newsessions.base.a.a(BaseIntentExecutor.java:99)
05-22 11:35:37.470 E/AndroidRuntime(  631):     at com.newsessions.base.a.e(BaseIntentExecutor.java:76)
05-22 11:35:37.470 E/AndroidRuntime(  631):     at com.newsessions.base.a.a(BaseIntentExecutor.java:67)
05-22 11:35:37.470 E/AndroidRuntime(  631):     at com.newsessions.cmd.general.volume.VolumeChangeExecutor.b(VolumeChangeExecutor.java:28)
05-22 11:35:37.470 E/AndroidRuntime(  631):     at com.newsessions.cmd.general.volume.a.a(LowerVolumeExecutor.java:63)
05-22 11:35:37.470 E/AndroidRuntime(  631):     at com.newsessions.base.a.d(BaseIntentExecutor.java:44)
05-22 11:35:37.470 E/AndroidRuntime(  631):     at com.newsessions.base.b.run(Unknown Source)
05-22 11:35:37.470 E/AndroidRuntime(  631):     at android.os.Handler.handleCallback(Handler.java:733)
05-22 11:35:37.470 E/AndroidRuntime(  631):     at android.os.Handler.dispatchMessage(Handler.java:95)
05-22 11:35:37.470 E/AndroidRuntime(  631):     at android.os.Looper.loop(Looper.java:136)
05-22 11:35:37.470 E/AndroidRuntime(  631):     at android.app.ActivityThread.main(ActivityThread.java:5001)
05-22 11:35:37.470 E/AndroidRuntime(  631):     at java.lang.reflect.Method.invokeNative(Native Method)
05-22 11:35:37.470 E/AndroidRuntime(  631):     at java.lang.reflect.Method.invoke(Method.java:515)
05-22 11:35:37.470 E/AndroidRuntime(  631):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:829)
05-22 11:35:37.470 E/AndroidRuntime(  631):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:645)
05-22 11:35:37.470 E/AndroidRuntime(  631):     at dalvik.system.NativeStart.main(Native Method)

in order to confirm it is the build cache reason, i clean then re-build on the changed code basically :

./gradlew clean
./gradlew :app:assembleRelease

that crash gone in the afterword app.

i attempt to create a demonstrating project to prove this problem, but that project doesn't popup the crash, only in my productive project.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!