• ProGuard常见问题及解决套路


    ProGuard是一个压缩、优化和混淆Java字节码的工具,非常好用。本篇文章总结一下许多人在使用ProGuard时经常遇到的问题。

    我把在使用ProGuard时经常遇到的问题分为两类,分别是导致构建失败的编译时问题,以及构建通过但运行时崩溃或结果不正确的运行时问题。大多数人所遇到的大多数问题,都可以在下面的内容中找到对应的解决套路。
    在开始讲这两类问题前,先明确一点:我们所说的添加混淆规则,不是指加入了才会混淆相关的类,相反,事实上,当你启用混淆之后,添加的一些诸如-keep xxxx的规则才是起着不混淆的作用。
    下面开始讲这两类问题。

    编译时问题

    问题

    首先讲编译时的问题。导致编译不通过,最常见的情况是这样的:
    在漫长的编译之后,我们等到的控制台上的这样一个输出结果:

    ...
    Note: there were 8 references to unknown classes.
          You should check your configuration for typos.
          (http://proguard.sourceforge.net/manual/troubleshooting.html#unknownclass)
    Note: there were 272 unkept descriptor classes in kept class members.
          You should consider explicitly keeping the mentioned classes
          (using '-keep').
          (http://proguard.sourceforge.net/manual/troubleshooting.html#descriptorclass)
    Note: there were 75 unresolved dynamic references to classes or interfaces.
          You should check if you need to specify additional program jars.
          (http://proguard.sourceforge.net/manual/troubleshooting.html#dynamicalclass)
    Warning: there were 11 unresolved references to classes or interfaces.
             You may need to add missing library jars or update their versions.
             If your code works fine without the missing classes, you can suppress
             the warnings with '-dontwarn' options.
             (http://proguard.sourceforge.net/manual/troubleshooting.html#unresolvedclass)
    Warning: Exception while processing task java.io.IOException: Please correct the above warnings first.
    
    

    原因及解决方法

    有些小伙伴会自动忽略英文日志,即便它给出了明确的答案,这是个严重的不良习惯。如上,其实在这段日志中,已经表明了原因及解决方案了。注意Warning开头的警告内容,最后一个警告是让你先解决第一个警告的内容,所以先忽略。我们看它前面的警告:

    there were 11 unresolved references to classes or interfaces.
             You may need to add missing library jars or update their versions.
             If your code works fine without the missing classes, you can suppress
             the warnings with '-dontwarn' options.
    
    

    这是什么意思呢?
    第一句话是告诉你原因:有11个未解析的类或引用
    后面两句是解决方案。方案一:你可能需要添加丢失的库或更新它们的版本。方案二:如果你现在代码运行得好好的,也就是没有它们也没关系,那你可以使用-dontwarn来禁止这样的警告。

    那么如何知道有哪些未解析的类或引用呢?
    遇到这样的问题,很简单,我们往上翻,最终肯定会找到Warning开头的日志内容:

    Warning: okhttp3.internal.platform.ConscryptPlatform: can't find referenced class org.conscrypt.OpenSSLProvider
    Warning: okhttp3.internal.platform.ConscryptPlatform: can't find referenced class org.conscrypt.OpenSSLProvider
    Warning: okhttp3.internal.platform.ConscryptPlatform: can't find referenced class org.conscrypt.Conscrypt
    Warning: okhttp3.internal.platform.ConscryptPlatform: can't find referenced class org.conscrypt.Conscrypt
    Warning: okhttp3.internal.platform.ConscryptPlatform: can't find referenced class org.conscrypt.Conscrypt
    Warning: okhttp3.internal.platform.ConscryptPlatform: can't find referenced class org.conscrypt.Conscrypt
    Warning: okhttp3.internal.platform.ConscryptPlatform: can't find referenced class org.conscrypt.Conscrypt
    Warning: okhttp3.internal.platform.ConscryptPlatform: can't find referenced class org.conscrypt.Conscrypt
    Warning: okhttp3.internal.platform.ConscryptPlatform: can't find referenced class org.conscrypt.Conscrypt
    Warning: okhttp3.internal.platform.ConscryptPlatform: can't find referenced class org.conscrypt.Conscrypt
    Warning: okhttp3.internal.platform.ConscryptPlatform: can't find referenced class org.conscrypt.Conscrypt
    
    

    它的句型是这样的:Warning: xxxx.xxx.ABC: can't find referenced class xxx.xxx.XYZ
    意思就是第三方库里的ABC引用了XYZ这个类,但是我在类路径中找不到XYZ这个类。
    这种情况很常见,比如一个Java库使用了另一个库的一些类来实现某个特性。如果你在使用这个库的时候需要这个特性,那么你就要把另一个库也加进来,也就是前面给出的方案一。而如果你不需要用到,如上面的例子,项目中实际上用不到org.conscrypt包里的内容,那么我们在混淆规则的文件中添加上-dontwarn规则就可以了。

    如何添加?

    规则很简单。

    -dontwarn 类名
    
    

    在上面的日志中,有引用的类,也有被引用的类,这两者都可以。也就是对于前面的句型,你既可以使用-dontwarn xxxx.xxx.ABC,也可以使用-dontwarn xxx.xxx.XYZ
    比如上面的例子,我们用

    -dontwarn okhttp3.internal.platform.ConscryptPlatform
    
    

    和用

    -dontwarn org.conscrypt.OpenSSLProvider
    -dontwarn org.conscrypt.Conscrypt
    
    

    都可以解决问题。但从这里也可以看出疑惑来了。如果有多个类呢?是不是每一个类都要写上去呢?
    当然不是。这里的类名是支持通配符的,比如上面,我们可以写为:

    -dontwarn org.conscrypt.*
    
    

    表示禁止org.conscrypt包下的类的警告。但是这里的*是不包含包分隔符的,也就是说它的子包里面的类是不会被禁止的。如果需要连它下面的包的类也一并禁止,可以使用包含包分隔符的**,也就是如下:

    -dontwarn org.conscrypt.**
    
    

    关于过滤器的更多用法,可以参阅文档:https://www.guardsquare.com/en/products/proguard/manual/usage#filters

    总结

    这是最常见的一类问题,我们来总结一下这个套路:

    1. 编译报错,提示内容是Warning: there were xxx unresolved references to classes or interfaces.
    2. 往上找带有Warning:的日志,句型结构为:Warning: xxxx.xxx.ABC: can't find referenced class xxx.xxx.XYZ
    3. 往混淆规则里添加-dontwarn 类名的规则。

    运行时问题

    我们再来看另一类问题。
    当有人在讨论群里求助混淆相关的问题时,往往会有人说加-keep。那么-keep是作什么用呢?在什么情况下需要它呢?

    在文章开头一句话介绍过,Proguard 是一个压缩、优化和混淆Java字节码文件的工具。也就是,它所做的不仅仅包括我们所知道的混淆,它会分析所有的类,找出没有用的类、字段、方法等把它们删除掉,从而达到对字节码的压缩及优化。而如果我们使用了反射去调用一些类或方法的话,它是不知道的,这样就会导致“误删”的情况。
    所以当开启混淆之后,出现类找不到,方法找不到,属性找不到时,我们就要使用keep相关规则来把它们给留住啦。比如使用-keep class xxxx {*;}保留指定的类名及其成员。
    keep规则有三种(Android新增加的@keep注解不谈,这里只讲规则文件的内容):

    • -keep 保留指定的类名及其成员
    • -keepclassmembers 只保留住成员,不能保留住类名
    • -keepclasseswithmembers 根据成员找到满足条件的所有类,保留它们的类名和成员名

    比如我们使用了事件总线,为使方法能保留,我们可以添加如下的规则:

    -keepclassmembers class ** {
        @org.greenrobot.eventbus.Subscribe <methods>;
    }
    
    

    表示保留所有类中的有@org.greenrobot.eventbus.Subscribe注解的方法。<methods>是指方法,如果要保留字段,则使用<fields>

    反正就是找不到类或成员,你就keep住。这里的keep规则也是很灵活的,比如除了上面指定的类,你也可以指定为接口:

    -keepclassmembernames,allowobfuscation interface * {
        @retrofit2.http.* &lt;methods&gt;;
    }
    
    

    可以指定继承自某个类的所有类,如:

    -keep public class * extends android.app.Service
    
    

    可以指定实现某个接口的类,如:

    -keep class * implements com.google.gson.TypeAdapterFactory
    
    

    可以指定某个包下的所有类(包括子包)及所有成员,如:

    -keep class com.tencent.stat.**  {* ;}
    
    

    除此之外,我们还可能会遇到其他问题,这里把其他常用的规则也快速过一下:

    注解解析不到?

    -keepattributes *Annotation*
    
    

    泛型转换失败?

    -keepattributes Signature
    
    

    枚举?

    -keepclassmembers enum * { *; }
    
    

    掌握以上几条规则,通常可以解决绝大多数混淆以后运行出错的问题了。

    本篇讲到这里,希望对被以上常见问题所困扰的同学有所帮助。这里需要注意一下,对于第一类问题,在编译的时候就能暴露出来,而第二类问题,只有在运行到相关代码时才会出现。所以如果一个项目以前是不使用混淆的话,在启用混淆之后一定要做好回归测试。

  • 相关阅读:
    [转]Delphi DLL的创建、静态 以及动态调用
    Delphi txt文件的操作(读取、写入)
    为什么要使用动态链接库(DLL)
    TStringGrid 实现下拉框
    Ryzen 4000'Vermeer' CPU和Radeon RX'Big Navi'图形卡
    AMD Ryzen 5000‘Cezanne’APU
    AMD–7nm “Rome”芯片SOC体系结构,支持64核
    ARM Cortex-M嵌入式C基础编程(下)
    ARM Cortex-M嵌入式C基础编程(上)
    基于ARM Cortex-M的SoC存储体系结构和实战
  • 原文地址:https://www.cnblogs.com/lalalagq/p/9964947.html
Copyright © 2020-2023  润新知