Proguard是一个代码优化和混淆工具。能够提供对Java类文件的压缩、优化、混淆,和预校验。压缩的步骤是检测并移除未使用的类、字段、方法和属性。优化的步骤是分析和优化方法的字节码。混淆的步骤是使用短的毫无意义的名称重命名剩余的类、字段和方法。压缩、优化、混淆使得代码更小,更高效。
一、Proguard简单使用:
在build.gradle文件中默认的Proguard文件配置是false关闭的,我们可以在debug模式下打开,设置为true即可:
默认的getDefaultProguardFile(‘proguard-android.txt’) 会使用build/intermediates/下面的文件(可以println 查看),如果在这个目录下没有则可以通过运行gradle任务:
生成。而后面的’proguard-rules.pro’则是一个相对路径,相对于这个build.gradle同级目录下的proguard-rules.pro
然后在项目的gradle task中双击extractProguardFiles任务,就可以看到生成了三个混淆文件:
我们可以将proguard-android文件拷贝到我们项目中,看下系统生成的keep规则使用
可以看到不需要混淆的情况有如下一些:
- native层代码;
- 所有继承自view的组件;
- activity里面的所有view;
- 枚举;
- Parcelable;
- 静态内部类;
- webkit接口;
- 所有support包、androidx下面的类;
- 所有使用了keep注解的类、方法、成员变量、构造方法
简单语法:
-keep 指定类和类成员(变量和方法)不被混淆。
(保护了类名)
-keep class com.dongnao.proxy.guard.test.Bug
(保护了 public static void的没有参数的函数)
-keep class com.dongnao.proxy.guard.test.Bug{
public static void *();
}
(保护所有)
-keep class com.dongnao.proxy.guard.test.Bug{
*;
}
-keepclassmembers 指定类成员不被混淆(就是-keep的缩小版,不管类名了)。
(都被混淆了)
-keepclassmembers class com.dongnao.proxy.guard.test.Bug
-keepclasseswithmembers 指定类和类成员不被混淆,前提是指定的类成员存在。
(保护类名,但是没指定成员,所以函数名被混淆)
-keepclasseswithmembers class com.dongnao.proxy.guard.test.Bug
-keepclasseswithmembers class com.dongnao.proxy.guard.test.Bug{
native <methods>;
}
实验项目地址:https://github.com/buder-cp/base_component_learn/tree/master/performanceOPT/buderdn08
开启混淆后,我们新建的User类,如果没有使用到,那么就不会打包到dex文件中,可以看到并没有User类:
ProGuard 会移除所有(并且只会移除)未使用的代码。不过,ProGuard 难以对许多情况进行正确分析,可能会移除应用真正需要的代码。比如需要反射、动态加载所引用的类等情况,可能因为proguard移除或者混淆了这部分没使用的类,而导致错误。所以有时需要编写混淆优化配置文件。在gradle中的proguardFiles 能够让我们传递File文件或者文件路径交给proguard来执行。
当我们在MainActivity中使用这个User类后,
User user = new User();
user.age = 18;
user.name = "buder";
并且添加混淆规则,不混淆User类里面的String字段:
-keep class com.test.buderdn08.User {
java.lang.String *;
}
最终的结果,User类中String成员变量是name,并没有被混淆掉。
很多混淆命令可以多多尝试使用。
二、Proguard生成的混淆代码,如何对应原始代码
outputs/mapping/debug/mapping.txt文件中保存了混淆前后的对应关系,混淆后的代码错误栈恢复方法:
1.先配置 -keepattributes SourceFile,LineNumberTable
2.把错误信息保存到文件
3.使用工具 sdk/tools/groguard/bin/retrace.bat
再执行 retrace.bat -verbose mappint文件 bug文件
我们在Activity中主动写一个bug,运行产生这样的日志:
这个日志有两个问题:
- 我们不知道是哪个类报告出的。
- 不知道这个类是哪一行报出的。
第一个问题很显然是由于我们的代码进行了混淆。
在开启混淆后,我们生成apk会产生
dump.txt:
说明 APK 中所有类文件的内部结构。这个文件内容非常多,可读性也并不高
mapping.txt:
提供原始与混淆过的类、方法和字段名称之间的转换。
seeds.txt:
列出未进行混淆的类和成员。我们保护的zip没有被混淆
usage.txt
:列出从 APK 移除的代码。可以在这个文件中查看是不是有我们不想被移除的类
最重要的当然就是我们的mapping文件了。通过这个文件我们就能定位到:
出现错误的函数是Bug类中的test函数。
我们现在比较简单,所以一下子就能定位到错误函数,如果函数层级较深,光靠自己比对来查找难度就比较大了。所以sdk中提供了一个工具在tools/proguard/bin中。
我们把logcat里面保存的错误堆栈复制到一个文件中:
然后执行retrace 脚本(在 Windows 上为 retrace.bat;在 Mac/Linux 上为 retrace.sh)
retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>]
现在错误日志一句mapping还原了。所以我们每次在发布版本之后都需要保留这个mapping文件。所以一般异常上报平台都会提供mapping上传然后帮助我们分析。
第一个问题解决的,第二个问题是行数显示的是Unknow Source
如果希望出现具体行数,我们需要在配置文件中加入
抛出异常时保留代码行号,在异常分析中可以方便定位
-keepattributes SourceFile,LineNumberTable
另外还可以配合-renamesourcefileattribute AAA
使用字符串"AAA"来替代真正的类,避免泄漏更多的信息