为什么把这三个内容放到一起说?因为在 Android Gradle 中配置方法基本是在一起的。
官方说明如下:
为了尽可能减小应用的大小,您应在发布 build 中启用缩减功能来移除不使用的代码和资源。启用缩减功能后,您还会受益于两项功能,一项是混淆处理功能,该功能会缩短应用的类和成员的名称;另一项是优化功能,该功能会采用更积极的策略来进一步减小应用的大小。本页介绍 R8 如何为项目执行这些编译时任务,以及您如何对这些任务进行自定义。
当您使用 Android Gradle 插件 3.4.0 或更高版本构建项目时,该插件不再使用 ProGuard 执行编译时代码优化,而是与 R8 编译器协同工作,处理以下编译时任务:
- 代码缩减(即摇树优化):从应用及其库依赖项中检测并安全地移除不使用的类、字段、方法和属性(这使其成为了一个对于规避 64k 引用限制非常有用的工具)。例如,如果您仅使用某个库依赖项的少数几个 API,那么缩减功能可以识别应用不使用的库代码并仅从应用中移除这部分代码。如需了解详情,请转到介绍如何缩减代码的部分。
- 资源缩减:从封装应用中移除不使用的资源,包括应用库依赖项中不使用的资源。此功能可与代码缩减功能结合使用,这样一来,移除不使用的代码后,也可以安全地移除不再引用的所有资源。如需了解详情,请转到介绍如何缩减资源的部分。
- 混淆:缩短类和成员的名称,从而减小 DEX 文件的大小。如需了解详情,请转到介绍如何对代码进行混淆处理的部分。
- 优化:检查并重写代码,以进一步减小应用的 DEX 文件的大小。例如,如果 R8 检测到从未采用过给定 if/else 语句的
else {}
分支,则会移除else {}
分支的代码。如需了解详情,请转到介绍代码优化的部分。默认情况下,在构建应用的发布版本时,R8 会自动执行上述编译时任务。不过,您也可以停用某些任务或通过 ProGuard 规则文件自定义 R8 的行为。事实上,R8 支持所有现有 ProGuard 规则文件,因此您在更新 Android Gradle 插件以使用 R8 时,无需更改现有规则。
开启混淆功能
上面没有提到的一个代码混淆的重要作用:我们知道 apk 文件是相对容易被反编译的,未加混淆的 apk,反编译后基本裸奔。而混淆的 apk 即使被反编译,类名与变量名都会处理成无意义的字符,很大程度上降低了源码的可读性。
以下是在 Android Studio 中开启混淆的方法和常用的配置。Android Studio 的 Gradle 混淆功能默认是关闭的,我们需要手动在 module 的 build.gradle 中打开。
- 在 Module 的 build.gradle 中添加如下配置:
1 | android{ |
之后我们需要在 proguard-rules.pro 文件中编辑混淆规则。
proguard 手册
输入/输出配置项
@filename
等同于:-include filename,filename 文件名。
-include [filename]
从给定文件中读取配置项,filename 文件名。
-basedirectory [directoryname]
为后续的相对文件指定基础目录,directoryname 目录名。
injars [classPath]
指定要处理的输入 jar(等压缩包),包中的 class 文件会被处理后输出到 outjars 的指定路径下,非 class 文件直接输出到 outjars 指定路径,classpath jar 包输入路径。outjars [classPath]
指定输出路径,接收 injars 的文件输出,classPath 输出路径。libraryjars [classPath]
指定不被混淆的 jar,classPath 文件路径。
压缩配置项
-dontshrink
不进行代码压缩,默认会进行代码压缩,打开此配置项则不进行压缩。
-printusage [fileName]
将被压缩的代码输出到指定路径。
优化配置项
-dontoptimize
指定不进行代码优化,默认情况下 ProGuard 会优化所有代码。
-optimizations [optimization_filter]
在更细粒度的级别指定要启用和禁用的优化,篇幅有限不展开讲了,具体可以参考:Configuration - Optimizations
常用的配置方式:
-optimizations !code/simplification/cast,!field/*,!class/merging/*
-optimizationpasses n
指定代码迭代优化的次数,默认执行一遍。
混淆配置
-dontobfuscate
不进行混淆,默认会进行代码混淆。
-printmapping [filename]
将混淆前后映射表输出到指定文件。
-dontusemixedcaseclassnames
混淆时不使用大小写混合,混淆后类名为小写。
-keepattributes [attribute_filter]
指定要保留的属性,如:
-keepattributes Exceptions,InnerClasses
,具体可参考:Optional attributes
通用配置项
-verbose
指定在处理期间输出更多信息。如果程序因异常终止,此选项将打印出整个堆栈跟踪,而不仅仅是异常消息。
-dontnote [class_filter]
指定不打印匹配类的相关异常信息,class_filter 为正则表达式,符合表达式的类不打印信息。
-dontwarn [class_filter]
指定相关类不发出警告,class_filter 为正则表达式。如:
-dontwarn androidx.**
-dump [filename]
将所有 class 的内部结构输出到指定文件。如:
-dump proguard/class_files.txt
keep 配置项
-keep []
指定要保留的类和类成员。
1
2
3
4
5
6
7
8
9
10//View子类,类名不混淆
- keep public class * extends android.view.View{
//set开头的方法不混淆
void set*(***);
//构造函数不混淆
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
复制代码指定的类名和类成员不被混淆,需要注意的是以下配置只能保证类名不被混淆。
1
2-keep public class * extends android.app.Activity
复制代码想要类名和类成员全部不混淆应该使用:
1
2-keep public class * extends android.app.Activity{*;}
复制代码-keepclassmembers
保留指定的类成员。
1
2
3
4
5
6//指定不混淆的类成员,类名会混淆
-keepclassmembers class * {
//事件监听类(参数符合**On*Event)的方法不混淆
void *(**On*Event);
}
复制代码-keepclasseswithmembers
在所有类成员都存在的情况下,指定要保留的类和类成员。
1
2
3
4
5
6//需要匹配类和所有指定成员都存在的类,类和类成员不混淆
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
复制代码功能与
-keep
很像,举个例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class MyClass extends android.view.View{
public MyClass(Context context) {
super(context);
}
public MyClass(Context context, { AttributeSet attrs)
super(context, attrs);
}
public MyClass(Context context, int defStyleAttr) { AttributeSet attrs,
super(context, attrs, defStyleAttr);
}
/**
* 自定义方法
* @param onClickListener
*/
public void addListener(OnClickListener onClickListener){
//TODO: todo
}
}
复制代码当使用
-keep
时:1
2
3
4
5
6
7
8
9
10- keep public class * extends android.view.View{
//构造函数不混淆
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
public void addListener(***)
public void getListener(***)
}
复制代码MyClass 类名和指定的类成员不会被混淆(未指定的类成员也会被混淆),因为 MyClass 符合
class * extends android.view.View
条件。而使用
-keepclasseswithmembers
时:1
2
3
4
5
6
7
8
9
10- keep public class * extends android.view.View{
//构造函数不混淆
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
public void addListener(***)
public void getListener(***)
}
复制代码MyClass 会被混淆,因为 MyClass 不符合
public void getListener(***)
条件。当keepclasseswithmembers
指定的类和类成员全部存在的情况下,才会匹配该类(MyClass 不存在getListener()
方法,所以不能被匹配)。-keepnames/-keepclassmembernames和-keepclasseswithmembernames
选项 等同于 作用 -keepnames ** -keep,allowshrinking ** 指定要保留的类和类成员,允许代码压缩优化,成员可能在压缩阶段被删除 -keepclassmembernames ** - keepclassmembers,allowshrinking ** 指定要保留的类成员,允许代码压缩优化,成员可能在压缩阶段被删除 -keepclasseswithmembernames ** -keepclasseswithmember,allowshrinking ** 在所有类成员都存在的情况下,指定要保留的类和类成员,允许代码压缩优化,成员可能在压缩阶段被删除 简单来说使用
-keep/-keepclassmember和-keepclasseswithmember
可能会阻止成员被压缩优化,而使用-keepnames/-keepclassmembernames和-keepclasseswithmembernames
而不会。
Keep 配置项修饰符
可用用来修饰
-keep/-keepclassmember和-keepclasseswithmember
,使用方式:
1 | -keep[,modifiter,...] class_specification |
allowshrinking
上面提到过了,允许对象进行代码压缩,对象可能在压缩阶段被删除。
allowoptimization
指定的对象可能会被改变(优化步骤),但可能不会被混淆或者删除。
allowobfuscation
指定对象可能会被重命名,但是不会被删除和优化。
includedescriptorclasses
includecode
这两个自行了解吧,太不常用了。(其实我也没搞懂。。。)
通配符
符号 | 意义 | 人话 |
---|---|---|
? |
matches any single character in a class name, but not the package
separator. For example, "com.example.Test? " matches
"com.example.Test1 " and "com.example.Test2 ",
but not "com.example.Test12 ". |
匹配名称中的任意单个字符。 |
* |
matches any part of a class name not containing the package
separator. For example, "com.example.*Test* " matches
"com.example.Test " and
"com.example.YourTestApplication ", but not
"com.example.mysubpackage.MyTest ". Or, more generally,
"com.example.* " matches all classes in
"com.example ", but not in its subpackages. |
匹配名称中任意多个字符,不包含包分隔符和目录分隔符,用在类体中可以匹配任意字段和方法。 |
** |
matches any part of a class name, possibly containing any number of
package separators. For example, "**.Test " matches all
Test classes in all packages except the root package. Or,
"com.example.** " matches all classes in
"com.example " and in its subpackages. |
匹配名称中任何部分,可以包含分隔符。 |
<n> |
matches the n'th matched wildcard in the same option. For
example, "com.example.*Foo<1> " matches
"com.example.BarFooBar ". |
在相同选项中匹配第 n 个匹配的通配符。 |
篇幅有限只列举了部分常用的配置项,详细的说明可以查询:ProGuard 手册。
proguard 常用配置规则
常用文件配置如下:
1.常用配置
1 | -optimizationpasses 5 |
2. Android 系统类
android 类
1 | # Android类 |
support 包
1 | # support |
androidx 包
1 | # androidx |
自定义控件 get/set 和构造
1 | # 自定义控件 |
R 文件
1 | # R文件 |
webview
1 | # webview |
android 事件方法
1 | # 按键等事件 |
枚举类型
1 | -keepclassmembers enum * { |
其他类
1 | # native方法 |
3. 第三库
ButterKnife
1 | -keep class butterknife.** { *; } |
OkHttp
1 | -dontwarn com.squareup.okhttp3.** |
glide 3
1 | -keep public class * implements com.bumptech.glide.module.GlideModule |
glide 4
1 | -keep public class * implements com.bumptech.glide.module.AppGlideModule |
gson
1 | -keep class com.google.gson.** {*;} |
greendao
1 | -keepclassmembers class * extends org.greenrobot.greendao.AbstractDao { |
Retrofit2
1 | -dontwarn retrofit2.** |
RxJava、RxAndroid
1 | -dontwarn sun.misc.** |
Picasso
1 | -keep class com.parse.*{ *; } |
fastJson
1 | -dontwarn com.alibaba.fastjson.** |
EventBus
1 | # EventBus2 |
阿里云推送
1 | -keepclasseswithmembernames class ** { |
以上只是列举了一些 Android 系统和第三方库常用的混淆配置方式,项目中需要按照实际需求进行配置。
proguard 模版
- Android Studio混淆模板1