sdk 打包必备,proguard 混淆规则如何配置
前言
作为一名 Android 开发者,如果你不想你的 app 或者 sdk 裸奔,那么在发布之前对代码混淆是必须的,它可以把类名、属性名和方法名变成毫无意义的 a,b,c 等。总得来说混淆带来两个好处:
- 一定程度上减小包的体积
- 使代码反编译之后难以阅读
混淆概念相对比较容易,很多 Android 开发者或多或少的都了解一些。那么对于混淆规则的配置有没有深入的研究呢?到底哪些代码应该混淆,哪些代码应该保留呢?很多时候是靠启动 CV 大法东拷贝一句西拷贝一句,搞到最后混淆规则配置文件杂乱无章。下面我就分享下 Android 中 ProGuard 那些事
ProGuard 简介
ProGuard 官网地址:https://www.guardsquare.com/proguard
ProGuard 的功能有四个:压缩、优化、混淆以及预校验;压缩环节会检测和移除无用的类、字段、方法和属性;优化环节会优化字节码和删除未使用的指令;混淆环节会用无意义的短变量去重命名类、字段以及方法;最后的预验证步骤向类添加预验证信息,这是 Java Micro Edition 和 Java 6 及更高版本所必需的,在 Android 开发中不需要。整个过程如下图所示:
Android Studio 中启用 ProGuard
Android Studio 集成 Java 语言的 ProGuard 作为压缩,优化和混淆工具,配合 Gradle 构建工具使用很简单,只需要在工程应用目录的 gradle 文件中设置 minifyEnabled 为 true 即可
1 |
|
上面的配置表示对 release 版本进行混淆处理。proguardFiles 指定了配置混淆规则的文件,可以是多个文件,最后合并多个配置文件为一个
- getDefaultProguardFile(‘proguard-android-optimize.txt’) 方法从 Android SDK 安装目录的 tools/proguard/ 文件夹中获取默认的规则。默认只有压缩和混淆两个功能,想要进一步使用字节码优化功能请使用同一目录下的 proguard-android-optimize.txt 文件。
注意:从 Gradle 插件 2.2 版本开始,使用的是 Gradle 插件中内置的配置文件,可以在
- proguard-rules.pro 用于添加自定义的混淆规则,例如保留一些不想被删除或混淆的代码。该文件默认位于 build.gradle 同级目录下。
- 一般还会配合 shrinkResources 来进行资源压缩,Android Studio 3.0 之后在 library 中不能配置 shrinkResources 否则报如下错误
个人理解这个问题原因是 library 中的资源可能被外部引用,单独构建 library 的时无法确定资源是否应该删除
ProGuard 语法
ProGuard 语法的基本符号
- 表示一条规则的开始
Keep 选项的语法
keep选项 | 描述 | 压缩 | 混淆 |
---|---|---|---|
-keep | 保留指定的类和类成员,防止被移除或混淆 | 不压缩 | 不混淆 |
-keepnames | 不混淆指定的类和类成员 | 压缩 | 不混淆 |
-keepclassmembers | 保留指定类中的类成员,防止被移除或混淆。假如类未被保留则失效 | 不压缩 | 不混淆 |
-keepclassmembernames | 不混淆指定类中的类成员 | 压缩 | 不混淆 |
-keepclasseswithmembers | 保留类中成员及包含它的类,防止被移除或混淆 | 不压缩 | 不混淆 |
-keepclasseswithmembernames | 不混淆类中成员及包含它的类 | 压缩 | 不混淆 |
这三组看起来很容易混淆,其实掌握其中的关键就很好区分:
- 带 name 后缀的表示只是防止混淆,若是没有被使用到还是可被移除;而没有带 name 后缀的表示防止被混淆和移除
- -keep 表示保留指定的类和类成员,而 -keepclasseswithmembers 表示保留类中成员及包含它的类。例如:-keep class * {native
;} 表示保留所有的类和其中的 native 方法;-keepclasseswithmembers class * {native ;} 表示保留包含 native 方法的类和类中的 native 方法
keep选项 | 描述 | 常用值 | 常用值说明 |
---|---|---|---|
-keeppackagenames | 指定不混淆给定的包名称,接受逗号分隔的包名称列表 | -keeppackagenames packagename1 | |
-keepattributes | 指定要保留的任何可选属性,接收逗号分隔的属性列表 | Annotation、SourceFile、InnerClasses、EnclosingMethod、Signature、LineNumberTable | 注解、源文件的名称、内部类、定义类的方法、类、字段或方法的通用签名、方法的行号 |
-keepparameternames | 指定保留参数名称和方法 |
其他可选属性请看 https://www.guardsquare.com/manual/configuration/attributes
通配符
类名
通配符 | 含义 |
---|---|
? | 匹配任意单个字符,包名分隔符(.)除外 |
* | 匹配除(.)外的任意字符(不匹配子包) |
** | 匹配任意字符(匹配子包) |
字段和方法
通配符 | 含义 |
---|---|
<fields> | 匹配所有字段 |
<methods> | 匹配所有方法,不包含构造方法 |
匹配所有构造方法 | |
? | 匹配任意单个字符 |
* | 匹配除(.)外的任意字符,匹配任意字段和方法 |
类型
通配符 | 含义 |
---|---|
% | 匹配任意原始数据类型,例如 boolean、int,不包括 void |
** | 匹配任意字符,不匹配基础数据类型、数组、void |
*** | 匹配任意类型 |
… | 匹配任意参数个数,任意参数类型 |
修饰符
修饰符 | 描述 |
---|---|
public | 公共,通常和 class、 |
private | 私有,通常和 class、 |
protected | 包内公共,通常和 class、 |
native | 本地,通常和 |
extends | 继承,通常和 class 配合使用。例: -keep class * extends android.app.Activity |
implements | 实现,通常和 class 配合使用。例:-keep class * implements android.view.OnClickListener |
其他
符号 | 描述 | 例子 |
---|---|---|
includedescriptorclasses | 不混淆方法和字段的类型描述符中的任何类,默认只保留基础数据类型 | -keep, includedescriptorclasses |
-dontwarn | 不对未解析的引用和其他重要问题发出警告 | -dontwarn twitter4j.** |
-dontnote | 不打印潜在的错误或疏漏的注释 | -dontnote |
-dontoptimize | 关闭优化 | -dontoptimize |
-verbose | 混淆过程中记录日志 | -verbose |
更多配置请看proguard官网 https://www.guardsquare.com/manual/configuration/usage
哪些类和类成员不应该被混淆
- 需要暴露给外部调用的类和类成员(基本上是 public 方法和字段)
- 需要暴露给外部使用的内部类或者接口
保留内部类或者接口写下如下,内部类和外部类用 $ 衔接
1 |
|
- 包名不混淆,否则很容易和其他库冲突
默认规则文件 proguard-android-optimize.txt 中的 -allowaccessmodification 配置会把混淆的类归到随机的包名下,如下图:
- 依赖的三方库保留原始代码不动
不公开代码的三方库代码已经混淆过了,无需重复混淆。公开代码的三方库如果提供了混淆规则就复制过来,若是没有提供混淆规则不必大费周章的去找哪些代码不应该被压缩混淆,除非对包体积有很高的要求。
无需配置一堆规则的简介写法
1 |
|
- 在 AndroidManifest.xml 配置的四大组件
四大组件被混淆之后无法与 manifest 注册的类匹配 - Java 的 native 方法
JNI 方法与定义的 native 方法名对应,混淆之后会找不到方法 - 反射使用的类、方法、属性
Class.forName(“”) 如类名被混淆将找不到类 - Json 对应的实体类
Gson 或者 Fastjson 的原理中涉及反射创建该类型的对象,反射需要用到完整类路径,混淆了会找不到 - JavaScript 调用 Java 的方法
- Layout 文件中使用到的自定义view 以及 set get 属性方法
Xml 配置的完整自定义 view 类路径,若自定义 view 混淆则找不到 - 在 Activity 中的方法参数是 view 的方法,即 layout 中定义的点击事件
- Parcelable、Serializable 序列化类
默认规则
1 |
|
自定义规则配置模板
1 |
|
混淆后的堆栈还原
代码经过 Proguard 优化混淆之后增加了反编译的难度,同时也带来线上堆栈信息定位困难。好在 Proguard 为我们提供了还原工具,先来看下 Proguard 每次构建后生成的内容
输出结果
混淆构建完成之后,会在
- configuration.txt
总的混淆规则配置文件,包含默认的和自定义的 - mapping.txt
提供混淆前后的内容对照表 - seeds.txt
罗列出未进行混淆处理的类和成员 - usage.txt
罗列出被移除的代码
混淆还原
体统为我们提供了 retrace 工具来还原混淆,retrace 工具结合 mapping.txt 就可以将混淆后的堆栈信息还原为正常情况下的堆栈信息。主要有两种方式来还原:
- 利用 retrace 脚本工具
脚本工具位于 Android Sdk 路径的 /tools/proguard/bin 目录中
Proguardgui.sh 是我们所需的脚本,将脚本拖到终端按回车就能看到如下 gui 界面
选择 ReTrace 一栏。导入 mapping.txt 文件,然后在 stack trace 中填写混淆后的堆栈信息,最后点击 ReTrace 按钮就能还原我们的堆栈信息,从而快速定位线上问题
- 利用 retrace 命令行
我们要先将崩溃信息保存到 txt 格式的文件中(如proguard.txt)保存,然后执行如下命令:
1 |
|
通过上述堆栈还原的分析可以发现最重要的是 mapping.txt 文件,里面记录了混淆前后的内容对照。如果 mapping.txt 文件丢失,堆栈将无法还原。所以每个版本的 mapping.txt 文件一定要保存好,最好有一个版本追溯(可以存在打包机上)
小知识
在 module 库中配置 consumerProguardFiles 可以将混淆规则一并打入库产物 aar 中,这样使用方使用我们的库将无需额外配置库对应的混淆规则
1 |
|
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!