类名 通配符如下:
通配符
含义
?
匹配单个字符,包名分隔符(.)除外
*
匹配除(.)外的任意字符
**
匹配任意字符(包含.),如com.rush.**匹配com.rush包下的所有类及其所有子包的类。
字段和方法 通配符如下:
通配符
含义
匹配所有构造方法
匹配所有字段
匹配所有方法
?
匹配单个字符,包名分隔符(.)除外
*
匹配除(.)外的任意字符
类型 通配符如下:
通配符
含义
%
匹配原始类型,如int, boolean等
?
匹配任意单个字符
*
匹配除包名分隔符(.)外的任意字符
**
匹配任意字符,包括包名分隔符(.)
***
匹配任意类型(原始类型、非原始类型、数组或非数组类型)
…
匹配任意参数个数,任意参数类型
其中类配置完整定义如下,其中[]表示可选:
[ @annotationtype] [[!] public| final| abstract|@ ...] [!] interface|class|enumclassname
[extends|implements [ @annotationtype] classname]
[{
[ @annotationtype] [[!] public| private| protected|static| volatile| transient...] |
(fieldtype fieldname);
[ @annotationtype] [[!] public| private| protected|static|synchronized| native| abstract|strictfp ...] |
(argumenttype,...) |
classname(argumenttype,...) |
(returntype methodname(argumenttype,...));
[ @annotationtype] [[!] public| private| protected|static ... ] *;
...
}]
keep过于简单粗暴,proguard提供了6种不同的配置:
保留
防止被移除或重命名
防止被重命名(未使用的会被移除)
类和类成员
-keep
-keepnames
仅类成员
-keepclassmembers
-keepclassmembernames
如类含有某成员,保留类及其成员
-keepclasseswithmembers
-keepclasseswithmembernames
3
其它常用配置
-verbose
指定在混淆过程中输出更多信息,配置这个选项后,在遇到异常时,将输出完整的堆栈,而不仅仅是异常消息。
-dontnote [class_filter]
指定不输出潜在的错误或者遗漏,比如拼写错误或者缺失了有用的信息。class_filter是一个正则表达式,匹配到类将不输出这些信息。
-dontwarn [class_filter]
指定一组类,不警告这些类中找不到引用或其它重要的问题。这个选项是很危险的,比如,找不到引用的错误可能导致代码不能正常work。
(在引用一些存在警告的jar包,这个选项比较有用。)
-ignorewarnings
指定输出所以警告信息,但继续进行混淆。同上一选项,慎用。
-printconfiguration [filename]
指定输出整个过程中的所有配置,输出到标准输出流或者指定文件中。这有时候在调度配置时有用。
-dump [filename]
指定在任一处理过程后,输出class文件的结构,可以输出到标准输出流或者指定文件中。
4
proguard配置示例
4.1 Android默认推荐配置
在IDE自动生成的project.properties文件中,有这样一行:
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
Android Studio默认生成的build.gradle文件有如下配置:
buildTypes{
release{
minifyEnabledfalse
proguardFiles getDefaultProguardFile( 'proguard-android.txt'), 'proguard-rules.pro'
}
}
其中getDefaultProguardFile('proguard-android.txt')获取的也是tools/proguard/proguard-android.txt。
下面看一下这个文件的配置:
# 不使用大小写混合类名
-dontusemixedcaseclassnames
# 不路过引用库中的非 public类
-dontskipnonpubliclibraryclasses
# 输出更多信息
-verbose
# 不进行优化
-dontoptimize
# 不进行预校验
-dontpreverify
# keep注解
-keepattributes *Annotation*
#keep google license服务接口
-keep publicclasscom.google.vending.licensing.ILicensingService
-keeppublicclasscom.android.vending.licensing.ILicensingService
# keepnative方法及其所属类
-keepclasseswithmembernamesclass* {
native ;
}
# keep自定义view的get/set方法
-keepclassmembers publicclass* extendsandroid.view.View{
voidset*(***);
*** get*();
}
# keep继续自Activity中所有包含public void *(android.view.View)签名的方法,如onClick
-keepclassmembers class* extendsandroid.app.Activity{
publicvoid*(android.view.View);
}
# keep枚举中的values和valueOf方法
-keepclassmembers enum* {
publicstatic**[] values();
publicstatic** valueOf(java.lang.String);
}
# keep Parcelable的CREATOR成员
-keepclassmembers class* implementsandroid.os.Parcelable{
publicstaticfinal android.os.Parcelable$Creator CREATOR;
}
# keep R文件的静态字段
-keepclassmembers class**.R$* {
publicstatic;
}
# 不输出support包中的警告
-dontwarn android.support.**
4.2 一个典型library库的配置
示例引用自官方文档samples
https://www.guardsquare.com/en/products/proguard/manual/examples#library
# 保存mapping映射文件到out. map
-printmapping out. map
# keep已keep方法的参数类型及参数名称
-keepparameternames
# 这个配置未弄清楚,待测试
-renamesourcefileattribute SourceFile
-keepattributes Exceptions,InnerClasses,Signature,Deprecated,
SourceFile,LineNumberTable,*Annotation*,EnclosingMethod
# keep所有类的protected成员
-keep publicclass* {
publicprotected*;
}
# keep在jdk 1.2中编译器插入的代码
-keepclassmembernames class* {
java.lang.Class class$(java.lang.String);
java.lang.Class class$(java.lang.String, boolean);
}
# keep native方法
-keepclasseswithmembernames,includedeorclasses class* {
native ;
}
# keep枚举中的values和valueOf方法
-keepclassmembers,allowoptimization enum* {
publicstatic**[] values();
publicstatic** valueOf(java.lang.String);
}
# keep系列化相关方法
-keepclassmembers class* implementsjava.io.Serializable{
staticfinal longserialVersionUID;
privatestaticfinal java.io.ObjectStreamField[] serialPersistentFields;
privatevoidwriteObject(java.io.ObjectOutputStream);
privatevoidreadObject(java.io.ObjectInputStream);
java.lang. Object writeReplace();
java.lang. Object readResolve();
}
4.3 一个典型Android App的配置
示例引用自官方文档samples
https://www.guardsquare.com/en/products/proguard/manual/examples#androidapplication
-dontpreverify
-repackageclasses ''
-allowaccessmodification
# 不优化算法指令
-optimizations !code/simplification/arithmetic
-keepattributes *Annotation*
# keep继承自系统组件的类
-keep public class* extendsandroid.app.Activity
-keep public class* extendsandroid.app.Application
-keep public class* extendsandroid.app.Service
-keep public class* extendsandroid.content.BroadcastReceiver
-keep public class* extendsandroid.content.ContentProvider
# keep自定义view及其构造方法、set方法
-keep public class* extendsandroid.view.View{
public (android.content.Context);
public (android.content.Context, android.util.AttributeSet);
public (android.content.Context, android.util.AttributeSet, int);
public void set*(...);
}
-keepclasseswithmembers class* {
public (android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembers class* {
public (android.content.Context, android.util.AttributeSet, int);
}
-keepclassmembers class* extendsandroid.content.Context{
public void *(android.view.View);
public void *(android.view.MenuItem);
}
-keepclassmembers class* implementsandroid.os.Parcelable{
static ** CREATOR;
}
-keepclassmembers class**.R$* {
public static ;
}
# keep java注释的方法,使用到webview js回调方法的需要添加此配置
-keepclassmembers class* {
@android.webkit.JavaInterface ;
}
5
关于反射
并不是所有会被反射引用的类都必须keep,在progurad过程中能直接分析到引用的类会被proguard做相应的处理:
# Class.forName的类名"SomeClass"被混淆后自动替换
Class.forName( "SomeClass")
SomeClass. class
# 以下字段和方法名都会在被混淆后自动替换
SomeClass. class.getField( "someField")
SomeClass. class.getDeclaredField( "someField")
SomeClass. class.getMethod( "someMethod", new Class[] {})
SomeClass. class.getMethod( "someMethod", new Class[] { A. class})
SomeClass. class.getMethod( "someMethod", new Class[] { A. class, B. class})
SomeClass. class.getDeclaredMethod( "someMethod", new Class[] {})
SomeClass. class.getDeclaredMethod( "someMethod", new Class[] { A. class})
SomeClass. class.getDeclaredMethod( "someMethod", new Class[] { A. class, B. class})
AtomicIntegerFieldUpdater.newUpdater(SomeClass. class, "someField")
AtomicLongFieldUpdater.newUpdater(SomeClass. class, "someField")
AtomicReferenceFieldUpdater.newUpdater(SomeClass. class, SomeType. class, "someField")
写个demo验证下:
Class> clazz = Class.forName( "com.rush.test.SimpleClass1");
clazz.getDeclaredMethod( "Test1");
SimpleClass2. class.getDeclaredField( "mTestField");
SimpleClass2. class.getDeclaredMethod( "Test2");
对以上代码编译并proguard,结果如下:
Class.forName(" com.rush.a.a") .getDeclaredMethod(" Test1", newClass[0]);
b.class.getDeclaredField(" a");
b.class.getDeclaredMethod(" a", newClass[0]);
通过Class.forName反射的class com.rush.test.SimpleClass1"被自动替换成了"com.rush.a.a";
但通过Class.forName获取的class再去反射方法没有正确处理;
通过完整class.getDeclaredField或者getDeclaredMethod反射时能够把字段名和方法名自动替换掉。
从结果看,反射并不是大家想像的那样必须keep,proguard能自动分析到引用的情况都能正确处理。但有些类是在配置文件里配置,或者动态拼接类名反射的,这些情况需要做好keep。
为了问题追踪的方便,建议所有会被反射引用的代码和library public接口都做好keep。
6
关于proguard配置的一些建议
所有会被反射引用的类都做好keep(建议,虽然有些反射能被正确处理)。 如native方法,四大组件,接口model,枚举,序列化类等。
只keep必须保留的内容,不要过度keep
使用热修复的App,添加-dontoptimize配置https://www.guardsquare.com/en/proguard/manual/introduction
https://www.guardsquare.com/en/proguard/manual/usage
https://www.guardsquare.com/en/proguard/manual/examples