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

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

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

ProGuard常见问题及解决套路的更多相关文章

  1. NHibernate常见问题及解决方法

    NHibernate常见问题及解决方法 曾经学过NHibernate的,但是自从工作到现在快一年了却从未用到过,近来要巩固一下却发现忘记了许多,一个"in expected: <end ...

  2. WebView加载本地html、js文件常见问题及解决办法

    声明:基于android studio平台,php语言搭建服务器 目录: 一.JavaScript脚本语言没有反应 二.alert无法弹出 三.html页面之间不能跳转 四.屏幕缩放没有达到预期效果 ...

  3. 转:WebTest的常见问题与解决

    WebTest的常见问题与解决录制好一个WebTest,加上各种规则,编辑后运行并不会像我们想象的那么顺利成功,往往会碰到很多问题,运行不成功的情况比较多,这样我们就遇到了如何解决这些问题的情形.1. ...

  4. WordPress源代码压缩优化及常见问题的解决

    先来看看效果: 意思就是让你的源代码看起来都挤在一起,这样如果别人想看你的源代码的话就不容易看懂了,(当然如果别人实在想看的话也可以通过某些软件的整理代码的功能来实现,比如IDEA的Ctrl+alt+ ...

  5. FineUIMvc 常见问题及解决办法

    Ø  简介 FineUIMvc 是基于 jQuery 的专业 ASP.NET MVC/Core 控件库,本文主要介绍 FineUIMvc 的常见问题及解决办法. 1.   View 中无法调用 Htm ...

  6. rsync @ERROR: auth failed on module backup 解决思路及附录rsync常见问题及解决办法

    昨晚小版本上线,使用rsync往服务器上传文件时,client报如下异常: @ERROR: auth failed on module backup rsync error: error starti ...

  7. fetch使用的常见问题及其解决办法

    摘自: https://segmentfault.com/a/1190000008484070 fetch使用的常见问题及其解决办法 javascript wonyun 2月25日发布 |   0 收 ...

  8. C#用ado.net访问EXCEL的常见问题及解决方法

    C#用ado.net访问EXCEL的常见问题及解决方法,除了像sql server,access常见的数据库,其实Excel文件也可以做为数据库访问. ado.net访问excel的实例: OleDb ...

  9. PHPmailer发送邮件时的常见问题及解决办法

    来源:http://www.chinastor.com/a/jishu/mailserver/0G392262014.html 使用PHPmailer发送邮件时的常见问题总结: 一,没有定义发送邮箱$ ...

随机推荐

  1. echart纵坐标标签特别长换行显示

    纵坐标 yAxis : [ { type : 'category', data : name, axisLabel: { //坐标轴刻度标签的相关设置. textStyle: { color: '#0 ...

  2. CSS Grid(CSS网格)

    Grid被设计来做一些Flexbox不能做的事情,所以不是被设计来取代Flexbox的. flexbox 一维的 Grid 二维的 总结:  Grid Items作用在Grid Container的直 ...

  3. laravel Specified key was too long; max key length is 1000 bytes

      [Illuminate\Database\QueryException]   SQLSTATE[42000]: Syntax error or access violation: 1071 Spe ...

  4. HDU 2604 Queuing( 递推关系 + 矩阵快速幂 )

    链接:传送门 题意:一个队列是由字母 f 和 m 组成的,队列长度为 L,那么这个队列的排列数为 2^L 现在定义一个E-queue,即队列排列中是不含有 fmf or fff ,然后问长度为L的E- ...

  5. tomcat闪退无法启动 the catalina_home environment variable is not defined correctly this environment variable is needed to run this program

    未成功配置CATALINA_HOME 1.计算机>属性>环境变量, 新建环境变量.变量名为CATALINA_HOME ,变量值tomcat的解压目录,注意后面不用多加“\”或者“;” 2. ...

  6. oracle 控制语句

    PL输出语句 set serverout on; -- 开启PL的输出语句功能declare n number:=1; -- 声明一个number型的变量n,并赋值为1 v varchar2(20): ...

  7. WinServer-服务器管理器-从入门到放弃

    WIN7 远程服务器管理工具 看看这篇帖子,他们说可以在WIN7上通过服务器管理工具来管理服务器上的软件 https://social.technet.microsoft.com/Forums/zh- ...

  8. Bing地图切片原理

    Bing地图切片系统 Bing地图提供了一个可以直接平移和缩放的世界地图.为了让地图操作更加平滑和及时响应,我们选择提前渲染地图不同层级的细节,并把每个层级的地图切割成为瓦片以便快速的还原展示.这篇文 ...

  9. 《UNIX环境高级编程》读书笔记之系统数据文件和信息(1)

    1.UNIX系统口令文件包括了下图所看到的的各字段,这些字段包括在<pwd.h>中定义的passwd结构体中 POSIX定义了两个获取口令文件项的函数. 在给出用户登录名或用户ID后.这两 ...

  10. httpClient模拟登陆校内某系统

    package com.huowolf; import java.util.ArrayList; import java.util.List; import org.apache.http.HttpE ...