首页 文章

使用方法引用在proguard之后使用NoSuchMethodError崩溃

提问于
浏览
8

编译和proguard之前的源代码:

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

编译和编译后的反编译代码:(用CFR 0_118反编译)

public class a extends superA {

    public void e() {
        super.e();
        this.c.a(this.j(), b.a((a)this)); // the problematic code here
    }
}

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

final class b implements c.a {
    private a a;

    b (a a1) {
        this.a = a1;
    }

    static /* synthetic */ b a(final a a) {
        return new b(a);
    }

    @LambdaForm.Hidden
    public void a() {
        this.a.finish();
    }
}

它仍然引用了 finish() 方法,该方法已被proguard标记为 m() .

I expect the reference finish() method to be obfuscated as m(), but this is not what is happening, and that's my question.

Proguard没有警告我,只有在遇到错误的代码时才会在运行时崩溃 NoSuchMethodError . 所以不要告诉我添加像我尝试的 -dontwarn java.lang.invoke.* 这样的proguard配置,但它没有用 .

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

我不想在finish()方法上添加 @Keep 注释,它是's a bad solution and I would have to worry about it and carefully use method references in the future, so I' m寻找最佳解决方案 .

以下是我的gradle配置:

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.** { *; }

4 回答

  • 0

    大多数情况下,您图书馆的网站/ github提供了必要的预备规则,如retrolamda

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

    Proguarding是一个跟踪错误的故事 . 检查您的日志记录以查看哪个库,类或组件导致问题,并将它们小心地添加到规则:) .

    您的错误NoSuchMethod具体:

    你的代码可能正在调用类似myClass.getMethod的东西,试图动态地找到一些方法 . 由于ProGuard无法始终自动检测到这种情况,因此必须使用适当的-keep选项保留缺少的方法:-keepclassmembers class mypackage.MyClass {void myMethod(); }

  • 0

    解决 -keep 无法解决的这些错误是一种真正的痛苦,而我唯一能够取得进展的方法就是遵循这一策略:

    • 弄清楚引入错误的proguard循环的哪个阶段(收缩,优化或混淆)

    • 添加/删除该步骤的异常,从最广泛的排除范围开始到最窄的范围,直到问题重新出现

    例如 .

    验证这是否是优化问题

    • 添加 -dontoptimize 而不是 -optimizations 字符串重建和测试

    • 如果崩溃得到缓解,请在最高级别 !method/*, !code/*, !class/*, !field/* 向后处理优化排除类别,直到您确定哪个排除使问题消失为止

    • 确定该排除类别中的最小排除(假设它是 !method/* ,从它到 !method/marking/* 然后如果有效则尝试 !method/marking/final . 如果有效,那么您已找到最小排除)

    这很可能是你正在使用的其中一个库或 in the version of Proguard you're using itself (我已经看过两个),所以也尝试更新它们 .

  • 2

    当一个类正在寻找或通过运行时的反射直接调用给定参数的方法时,会发生这种情况 . 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
        }
    
    }
    

    由于引用该方法使用了一个带有名称的字符串,proguard没有检测到它,并且在运行时,您会遇到崩溃 . 假设您无法修改代码,唯一的解决方案是避免模糊方法和类 .

    (您可以在Ormlite-Android中查看更实际的示例)

  • 0

    经过仔细的重新检查后,我得出结论,这可能不是一个关于proguard的错误,只有gradle .

    首先,我让源代码使用通用接口编码样式:

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

    然后我清理构建缓存并重建:

    ./gradlew clean
    ./gradlew :app:assembleRelease
    

    我执行输出发布应用程序并使其到达有问题的代码,它可以正常运行而不会崩溃 .

    这次我转向方法参考:

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

    但我没有在重建之前清理构建缓存:

    ./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.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)
    

    为了确认它是构建缓存原因,我基本上改变了代码 clean 然后 re-build

    ./gradlew clean
    ./gradlew :app:assembleRelease
    

    这个崩溃在后记应用程序中消失了 .

    我试图创建一个演示项目来证明这个问题,但该项目不会弹出崩溃,只在我的 生产环境 项目中 .

相关问题