Android 开发中为了代码安全一般都会使用 ProGuard 进行代码混淆,它可以把类名、属性名和方法名变为毫无意义的 a, b, c 等,但是有些代码是不需要混淆的,这时需要配置 proguard-rules.pro
文件。这是许多开发者对代码混淆的认识,但是 ProGuard 更深入的内容呢,如何配置混淆规则呢,下面我就分享下 Android 中 ProGuard 那些事。
先敲敲黑板,划下重点:本文内容重点在第三节 – 自定义要保留的代码
ProGuard 简介
ProGuard 官网地址:https://www.guardsquare.com/en/proguard
ProGuard is a Java class file shrinker, optimizer, obfuscator, and preverifier. The shrinking step detects and removes unused classes, fields, methods and attributes. The optimization step analyzes and optimizes the bytecode of the methods. The obfuscation step renames the remaining classes, fields, and methods using short meaningless names. These first steps make the code base smaller, more efficient, and harder to reverse-engineer. The final preverification step adds preverification information to the classes, which is required for Java Micro Edition and for Java 6 and higher.
ProGuard 的功能有四个:压缩、优化、混淆以及预校验。压缩环节会检测并移除无用的类、字段、方法和属性。优化环节会分析并优化方法的字节码。混淆环节会用无意义的短变量去重命名其余的类、字段以及方法。这些环节可以使得代码更精简,更高效,也跟难被逆向工程。预校验是针对 J2ME 和 Java 6 以上的,在 Android 开发中不需要。
Android Studio 中使用 ProGuard
本节内容引用自 Android 官网:https://developer.android.com/studio/build/shrink-code.html
启用 ProGuard
要在 Android Studio 中使用 ProGuard,请在 build.gradle
文件内相应的构建类型中添加 minifyEnabled true
。
例如,下面在 release 版本构建时启用代码压缩:
|
|
注:Android Studio 会在使用 Instant Run 时停用 ProGuard。如果您需要为增量式构建压缩代码,请尝试试用 Gradle 压缩器。
每次启用 ProGuard 构建时,都会在 <module-name>
/build/outputs/mapping/release/ 下输出下列文件:
dump.txt – 说明 APK 中所有类文件的内部结构。
mapping.txt – 提供原始与混淆过的类、方法和字段名称之间的转换。
seeds.txt – 列出未进行混淆的类和成员。
usage.txt – 列出从 APK 移除的代码。
配置 ProGuard 规则
上面的例子中除了 minifyEnabled 属性外,还有用于定义 ProGuard 规则的 proguardFiles 属性:
getDefaultProguardFile('proguard-android.txt')
方法会从 Android SDK 目录下的tools/proguard/
文件夹获取默认的 ProGuard 设置,默认只会使用压缩和混淆两个功能。
提示:想要进一步使用字节码优化功能,请使用位于同一位置的 proguard-android-optimize.txt 文件。它包含相同的 ProGuard 规则,但还包括其他在字节码一级(方法内和方法间)执行分析的优化,以进一步减小 APK 大小和提高运行速度。
注意:从 Android Gradle 插件 2.2 开始,使用的是 Gradle 插件中内置的配置文件,可以在 <root_project>
/build/intermediates/proguard-files/ 下看到,Android SDK 目录下的配置 ProGuard 配置文件不再维护了,也会被 Gradle 2.2 以上的插件忽略。
proguard-rules.pro
文件用于添加自定义的 ProGuard 规则,例如自定义保留一些不需要移除或混淆的代码。默认情况下,该文件位于模块根目录(build.gradle 文件旁)。
要添加更多各构建变体专用的 ProGuard 规则,请在相应的 productFlavor
代码块中再添加一个 proguardFiles 属性。例如,以下 Gradle 文件会向 flavor2 产品定制添加 flavor2-rules.pro。现在 flavor2 使用所有三个 ProGuard 规则,因为还应用了来自 release 代码块的规则。
|
|
转换混淆过的堆栈跟踪信息
在经过 ProGuard 混淆后,方法的名称都经过了混淆处理,堆栈跟踪信息的可读性变得很差。要将其转化成可读的堆栈信息,请使用 retrace 脚本(在 Windows 上为 retrace.bat;在 Mac/Linux 上为 retrace.sh)。它位于 <sdk-root>
/tools/proguard/ 目录中。该脚本利用 mapping.txt 文件和您的堆叠追踪生成新的可读堆叠追踪。使用 retrace 工具的语法如下:
|
|
如果不指定堆栈跟踪文件,retrace 工具会从标准输入读取。
在 Instant Run 中启动代码压缩
如果代码压缩在增量构建应用时非常重要,可以尝试使用 Android Gradle 插件内置的试用代码压缩器。也可以使用过与 ProGuard 相同的配置文件来配置 Android 插件压缩器。但是,Android 插件压缩器不会对您的代码进行混淆处理或优化,它只会删除未使用的代码。
要启用 Android 插件压缩器,只需在 “debug” 构建类型中将 useProguard 设置为 false(并保留 minifyEnabled 设置 true):
|
|
自定义要保留的代码
一般情况下,默认的 ProGuard 配置文件(proguard-android.txt)是不够,这样会移除所有未使用的代码,并且会混淆几乎所有(除了默认配置中保留的除外)的代码。但是有些情况下,我们是不想移除或混淆部分代码,例如:
Activity, Service, Receiver 等在 AndroidManifest.xml 文件中注册的类
JNI 中的 native 方法
反射使用的类,属性,方法
要保留不想被移除或混淆的代码,有两种方法:
一是在 proguard-rules.pro
文件中添加规则;
二是向需要保留的代码添加 keep
注解,需要引入 support-annotation 包。在类上添加 @Keep 可原样保留整个类。在方法或字段上添加它可完整保留方法 / 字段(及其名称)以及类名称。
一般都会使用第一种方法,因为规则都在这个文件中,方便查找和修改,不过 ProGuard 规则的语法一开始看可能难以理解,下面先看默认的 proguard-android.txt 中是如何写的。
默认的 ProGuard 规则
下面我列出的 <root_project>
/build/intermediates/proguard-files/ 中的 proguard-android.txt-2.3.3,后面 2.3.3 是因为我这里的 Gradle 插件的版本为 2.3.3。前面有提过,Gradle 插件 2.2 以后,使用的是插件内置的 ProGuard 规则。
下面文件中 #
开头的是源文件的注释,//
开头的是我添加的说明:
|
|
以上就是默认的 proguard-android.txt 中的所有规则,包含了常见的一些情况,但是在日常开发过程中,我们还是需要再自定义一些规则。不过 ProGuard 语法还是有些难以理解,例如上面的 keepclasseswithmembers
和 keepclasseswithmembernames
有什么区别呢?keepclassmembers
和 keepclasseswithmembers
又有什么区别呢?
ProGuard 语法
ProGuard 语法的官方文档:https://www.guardsquare.com/en/proguard/manual/usage
ProGuard 语法中的基本符号:
|
|
一些常见选项:
|
|
ProGuard 规则中最常用的是 Keep 选项,所以下面主要讲 Keep 选项的语法,ProGuard 中有 6 组 Keep 选项,以表格的方式显示如下:
Keep 选项 | 描述 | 压缩 | 混淆 |
---|---|---|---|
-keep | 保留类和类中的成员,防止被移除或混淆 | × | × |
-keepnames | 不混淆类和类中的成员 | √ | × |
-keepclassmembers | 保留类中的成员,防止被移除或混淆 | × | × |
-keepclasseswithmembers | 保留类中成员及包含它的类,防止被移除或混淆 | × | × |
-keepclassmembernames | 不混淆类中的成员 | √ | × |
-keepclasseswithmembernames | 不混淆类中的成员及包含它的类 | √ | × |
这六组选项看起来很容易搞混,其实只要掌握其中的关键差别就好区分了:(一)带 names
后缀的表示只是防止被混淆,还是可被移除,而没有 names
后缀则表示防止被混淆或移除;(二)classmembers
表示只对类中成员生效,而 classeswithmembers
表示不仅对类中成员生效,还对包含它的类生效;(三)keep
表示对类和类中匹配的成员生效,而 keepclasseswithmembers
表示对匹配的类成员及包含它的类生效,例如 -keep class * { native <methods>; }
表示保留所有的类和类中的 native 方法,-keepclasseswithmembers class * { native <methods>; }
表示保留所有的 native 方法及包含 native 方法的类。
Keep 选项后面的匹配条件中,经常需要用到通配符,例如上面默认 proguard-android.txt 规则中的 void set*(***);
,下面看看这些通配符的含义:
通配符 | 描述 |
---|---|
<init> |
匹配所有构造函数 |
<fields> |
匹配所有字段 |
<methods> |
匹配所有方法,不包括构造函数 |
? | 匹配任意单个字符 |
% | 匹配任意原始数据类型,例如 boolean、int,但是不包括 void |
* | 匹配任意长度字符,但是不包括包名分隔符( . ),例如 android.support.* 不匹配 android.support.annotation.Keep |
** | 匹配任意长度字符,包括包名分隔符( . ),例如 android.support.** 匹配 support 包下的所有类 |
*** | 匹配任意类型,包括原始数据类型、数组 |
… | 匹配任意数量的任意参数类型 |
注意:?
,*
和 **
都是不匹配原始数据类型和数组的,例如 void set*(**)
匹配 “void setObject(java.lang.Object obj)”,但是不匹配 “void setInt()” 和 “void setObject(java.lang.Object[] objs)”。
注意:”-keep class * extends android.view.View” 表示保留 View 的子类,方法和字段还是会被混淆的,而 “-keep class * extends android.view.View { *; }” 才表示保留所有 View 的子类及其成员。
在了解 Keep 选项和通配符后,在回头看默认的 proguard-android.txt 文件就轻松多了。
常用的自定义 ProGuard 规则
除了默认的 proguard-android.txt 文件的规则外,一般还需要在 proguard-rules.pro
中添加一些自定义的规则,下面是我总结的一些常用的自定义规则。
保留源代码行号
方便查看堆栈信息时对应源代码的位置。
|
|
Android 应用中常用规则
Android 开发中,一般需要保留 AndroidManifest.xml 定义的四大组件,自定义 View 的构造函数,参照自官网的 Android application sample。
|
|
混淆包名
|
|
不混淆 Serializable 相关内容
|
|
保留实体类
|
|
项目中用的一些开源库,只需要根据它们的建议添加相应的规则就可以了,这里就不再重复了。
这篇文章的内容就到这里了,感谢你耐心看到最后,希望对大家有所帮助,对于 ProGuard 还有疑问的欢迎在下面留言。