在程序引用的proguard之后使用NoSuchMethodError崩溃

compile&proguard之前的源代码:

public class IntentSession extends BaseIntentSession { @Override public void onResume() { super.onResume(); mExecutor.exec(getIntent(), this::finish); } } 

compile&proguard之后的反编译代码:(Decompiled with CFR 0_118)

 public class a extends superA { public void e() { super.e(); this.ca(this.j(), ba((a)this)); // the problematic code here } } 

现在是compile&proguard之后的关键代码, b类的反编译代码:

 final class b implements ca { private aa; b (a a1) { this.a = a1; } static /* synthetic */ ba(final aa) { return new b(a); } @LambdaForm.Hidden public void a() { this.a.finish(); } } 

它仍然引用已经被proguard混淆为m()finish()方法。

我期望引用finish()方法被模糊为m(),但这不是发生了什么,这就是我的问题。

Proguard没有发出警告,一旦碰到错误的代码,它在运行时就会与NoSuchMethodError一起崩溃。 所以不要告诉我添加一个像-dontwarn java.lang.invoke。*这样的尝试,但它没有工作的proguardconfiguration。

也许在混淆时涉及类的处理顺序是错误的,谁知道?

我不想在finish()方法上添加@Keep注解,这是一个糟糕的解决scheme,我将不得不担心它,并在将来谨慎使用方法引用,所以我正在寻找最佳的解决scheme。

下面是我的gradleconfiguration:

 dependencies { classpath 'com.android.tools.build:gradle:2.2.3' classpath 'me.tatarka:gradle-retrolambda:3.4.0' classpath "com.fernandocejas.frodo:frodo-plugin:0.8.3" classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' } 

以下是我的proguard-rules.pro

 -optimizationpasses 5 -dontusemixedcaseclassnames -dontskipnonpubliclibraryclasses -dontpreverify -verbose -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* -ignorewarnings -keep public class * extends android.app.Activity -keep public class * extends android.app.Application -keep public class * extends android.app.Service -keep public class * extends android.content.BroadcastReceiver -keep public class * extends android.content.ContentProvider -keep public class * extends android.app.backup.BackupAgentHelper -keep public class * extends android.preference.Preference -keep public class com.android.vending.licensing.ILicensingService -keepclasseswithmembernames class * { native <methods>; } -keepclasseswithmembers class * { public <init>(android.content.Context, android.util.AttributeSet); } -keepclasseswithmembers class * { public <init>(android.content.Context, android.util.AttributeSet, int); } -keepclassmembers class * extends android.app.Activity { public void *(android.view.View); } -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); } -keep class * implements android.os.Parcelable { public static final android.os.Parcelable$Creator *; } -dontwarn java.util.** -keep class java.util.** {*; } -dontwarn com.android.** -keep class com.android.** { *; } -dontwarn android.support.** -keep class android.support.** { *; } -keepattributes SourceFile, LineNumberTable # end common config ##---------------Begin: proguard configuration for Gson ---------- # Gson uses generic type information stored in a class file when working with fields. Proguard # removes such information by default, so configure it to keep all of it. -keepattributes Signature # Gson specific classes -keep class sun.misc.Unsafe { *; } #-keep class com.google.gson.stream.** { *; } -dontwarn com.google.gson.** -keep class com.google.gson.** { *; } -dontwarn com.baidu.util.audiocore.** -keep class com.baidu.util.audiocore.** { *; } # Application classes that will be serialized/deserialized over Gson ##---------------End: proguard configuration for Gson ---------- # Explicitly preserve all serialization members. The Serializable interface # is only a marker interface, so it wouldn't save them. -keepclassmembers class * implements java.io.Serializable { static final long serialVersionUID; private static final java.io.ObjectStreamField[] serialPersistentFields; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); } -keep public class * implements java.io.Serializable {*;} # end Serializable # ---------------------------- -dontnote -dontwarn com.xiaomi.push.service.XMPushService #for speech sdk -keep class com.orion.speech.** {*;} -keep class com.orion.speech.audio.** {*;} #end for speech sdk #for xiaomi -keep class PushReceiver {*;} -keep class com.xiaomi.push.**{*;} #end for xiaomi #for retrofit -dontwarn sun.misc.Unsafe -dontwarn okio.** # Platform calls Class.forName on types which do not exist on Android to determine platform. -dontnote retrofit2.Platform # Platform used when running on RoboVM on iOS. Will not be used at runtime. -dontnote retrofit2.Platform$IOS$MainThreadExecutor # Platform used when running on Java 8 VMs. Will not be used at runtime. -dontwarn retrofit2.Platform$Java8 # Retain generic type information for use by reflection by converters and adapters. -keepattributes Signature # Retain declared checked exceptions for use by a Proxy instance. -keepattributes Exceptions #end for retrofit #for lambda -dontwarn java.lang.invoke.* #end for lambda #for okhttp -keep class okhttp3.** { *; } -keep interface okhttp3.** { *; } -dontwarn okhttp3.** #end for okhttp #for RxJava -keep class rx.schedulers.Schedulers { public static <methods>; } -keep class rx.schedulers.ImmediateScheduler { public <methods>; } -keep class rx.schedulers.TestScheduler { public <methods>; } -keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* { long producerIndex; long consumerIndex; } -keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef { rx.internal.util.atomic.LinkedQueueNode producerNode; } -keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef { rx.internal.util.atomic.LinkedQueueNode consumerNode; } # end for RxJava #for bugly -dontwarn com.tencent.bugly.** -keep public class com.tencent.bugly.**{*;} #end for bugly #----------------android # this indicate the case of using APIs higher than minSDK (API 8) -dontwarn android.** # --------------------------------------- # TODO: can be reduce if we have more understanding about Service and AIDL -keep public class android.service.notification.** {*;} -keepattributes *Annotation*,EnclosingMethod -keepclasseswithmembers class * { public <init>(android.content.Context, android.util.AttributeSet, int); } -keepclasseswithmembernames class * { native <methods>; } -keepclasseswithmembers class * { public <init>(android.content.Context, android.util.AttributeSet); } -keepclasseswithmembers class * { public <init>(android.content.Context, android.util.AttributeSet, int); } -keepattributes *Annotation*,EnclosingMethod,Signature -keep interface android.content.pm.**{*;} -keep class android.content.pm.**{*;} -keep class android.os.Process{*;} -dontwarn com.android.internal.os.* -keep class android.support.v4.os.**{*;} -keepclassmembers class * { @android.support.v4 *; } # cmcm support -keep class com.cmcm.support.jni.** { *; } 

Solutions Collecting From Web of "在程序引用的proguard之后使用NoSuchMethodError崩溃"

通过这些错误修复这些bug并不是一个真正的痛苦,而且我唯一的方法就是遵循这个策略:

  1. 找出引入错误的阶段(收缩,优化或混淆)
  2. 添加/删除该步骤的exception,从最广泛的排除范围到最窄的范围,直到问题重新出现

例如

validation这是否是优化问题

  1. 添加-dontoptimize而不是-dontoptimizestring重build和testing
  2. 如果崩溃得到缓解,则通过最高级别的优化排除类来反向工作!method/*, !code/*, !class/*, !field/*直到您确定哪个排除使问题消失
  3. 确定排除的最小排除是什么(假设它是!method/* ,从它去到!method/marking/*然后如果这样做也可以试试!method/marking/final 。如果这样做的话,那么你已经发现了最小的排除)

这很可能你正在使用的一个库的Proguard规则的错误,或者是你正在使用的Proguard版本 (我已经看到两者),所以试着同时更新它们。

大多数时候,你的图书馆的网站/ github提供了必要的程序规则,如retrolamda :

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

Proguarding是一个小路错误的故事。 检查你的日志logging,看看哪些库,类或组件导致问题,并仔细添加到规则:)。

您的错误NoSuchMethod具体:

你的代码可能调用类似myClass.getMethod,试图dynamic地find一些方法。 由于ProGuard无法自动检测到这一点,因此必须使用适当的-keep选项来保留缺less的方法:

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

这发生在一个类正在寻找时,或者通过运行时reflection直接调用给定参数的方法。 Proguard无法警告您,因为编译时混淆了的类和消费者类之间没有链接。 你可以有类似的东西

 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 } } 

由于引用的方法使用了一个带有名称的string,因此proguard不会检测到它,并且在运行时会导致崩溃。 唯一的解决办法是,假设你不能修改代码,是为了避免混淆方法和类。

(你可以在Ormlite-Android中查看一个更现实的例子)

仔细重新检查之后,我得出的结论可能不是一个proguard的bug,只有gradle。

首先,我让源代码使用一般的界面编码风格:

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

然后我清理生成caching并重build:

 ./gradlew clean ./gradlew :app:assembleRelease 

我执行输出发布应用程序,并使其到达有问题的代码,它没有崩溃的工作。

这一次我转向方法参考:

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

但是我在重build之前没有清理构buildcaching:

 ./gradlew :app:assembleRelease 

现在重新执行与崩溃发生:

 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.aa(BaseIntentExecutor.java:99) 05-22 11:35:37.470 E/AndroidRuntime( 631): at com.newsessions.base.ae(BaseIntentExecutor.java:76) 05-22 11:35:37.470 E/AndroidRuntime( 631): at com.newsessions.base.aa(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.aa(LowerVolumeExecutor.java:63) 05-22 11:35:37.470 E/AndroidRuntime( 631): at com.newsessions.base.ad(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) 

为了确认它是构buildcaching的原因,我清理然后重新build立在更改后的代码基本上:

 ./gradlew clean ./gradlew :app:assembleRelease 

后遗症的应用程序崩溃了。

我试图创build一个演示项目来certificate这个问题,但是这个项目不会在我的生产性项目中popup崩溃。