在聊proguard使用之前,先说说proguard到底是什么东东,我主要做android开发,平时一般都听过android混淆打包的说法,直观的感觉就是把写好的java代码,通过一种编码方式给混淆了,让别人不容易看出代码逻辑以及java类之间的关系。其实,殊不知,这种混淆打包所依仗的就是这里要讲的progurad工具。
progurad工具实际上有四个功能。
压缩(Shrink):检测并移除代码中无用的类、字段、方法和特性(Attribute)。
优化(Optimize):对字节码进行优化,移除无用的指令。
混淆(Obfuscate):使用a,b,c,d这样简短而无意义的名称,对类、字段和方法进行重命名。
预检(Preveirfy):在Java平台上对处理后的代码进行预检,确保加载的class文件是可执行的。
但是在android混淆打包中,我们实际上只需要使用Obfusacate这个混淆功能。
我们是通过配置命令的方式来使用proguard的,而命令又需要一些参数什么的,这个就涉及到通配符的问题,这里就从命令、通配符以及proguard不能混淆三个部分来对proguard进行总结。
1、proguard命令
根据我的个人经验,proguard使用最多的命令就是keep以及dontwarn这两个命令一个,一个表示保留对应的类方法属性等不要混淆,另一个就是表示不对指定的类、包中的不完整的引用发出警告。关于proguard的命令真的很多,遇到不懂得可以到ProGuard 最全混淆规则说明这篇文章中去查找,或者查看proguard manual
我们知道android中混淆打包需要进行配置
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
这里有一个proguard-android.txt是基础配置,还有一个proguard-rules.pro这个文件中开发这可以添加自己的一些配置。我们就以proguard-android.txt文件中的配置来简单说说proguard命令吧。
-dontusemixedcaseclassnames #混淆时不生成大小写混合的类名,windows系统不能识别大小写
-dontskipnonpubliclibraryclasses #不忽略指定jars中的非public calsses
-verbose #混淆过程中打印详细信息,如果异常终止则打印整个堆栈信息
-dontoptimize #关闭优化
-dontpreverify #关闭预验证,加快编译速度
-keepattributes *Annotation* #指定要保留的任何可选属性,可以指定多个,用逗号分隔符分割
-keep public class com.google.vending.licensing.ILicensingService #指定对象不被混淆
-keep public class com.android.vending.licensing.ILicensingService
#指定保留的类和类成员,这里是指保留带native方法的类不被混淆
-keepclasseswithmembernames class * {
native <methods>;
}
#指定需要保留的类成员:变量或者方法,这里保留自定义控件的set/get方法不被混淆
-keepclassmembers public class * extends android.view.View {
void set*(***);
*** get*();
}
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}
-keepclassmembers class **.R$* {
public static <fields>;
}
#不对指定的类、包中的不完整的引用发出警告
-dontwarn android.support.**
#指定对象不被混淆
-keep class android.support.annotation.Keep
-keep @android.support.annotation.Keep class * {*;}
#指定保留的类和类成员,这里指带Keep annotation的类不被混淆
-keepclasseswithmembers class * {
@android.support.annotation.Keep <methods>;
}
-keepclasseswithmembers class * {
@android.support.annotation.Keep <fields>;
}
-keepclasseswithmembers class * {
@android.support.annotation.Keep <init>(...);
}
基础配置已经把常用的proguard命令都用上了,代码中也给出了解释,还有不懂得,请查看上面提到的两篇文章。
2、proguard的关键字和通配符
肯定有同学看了上面的配置后还是有疑惑,proguard命令知道了,那么第一小节代码中的*、…这些符号是啥啊,这些就是这一小节要讲的通配符了。
我们要指定混淆规则,但是项目中的类啊,方法什么的太多了,我们不能一个个去指定,那么通配符便大有用武之地了,通配符的作用便是通过符号统一指定一种类型的类、方法以及属性等的特殊符号。
那么关键字是什么呢,这个比较简单,就是配置代码中的class、interface、enum、extends、implements等字符。
class 关键字表示任何接口类、抽象类和普通类; interface 关键字表示只能是接口类; enum 关键字表示只能是枚举类。如果在 interface 和 enum 关键字前面加上感叹号(“ ! ”)分别表示不是接口类的类和不是枚举类的类,而 class 关键字前面不能加感叹号。
extends表示存在继承关系,而implements表示存在实现关系。
下面来分类说说通配符,proguard的“?”以及“*”应该是可以通用的,可以分别表示一个字符和n个字符(不含”.”)。但是对于类,方法、属性、以及参数、返回类型、参数类型等的通配符还是有一定差异的,下面直接给出作为参考。
类名
1) ? :问好代表一个任意字符,但不能是句号(“ . ”,因为句号是包名分隔符);
2) * :单个星号代表任意个任意字符,但不能代表句号”.”
3) ** :两个星号代表任意个任意字符,且能代表句号
成员变量
1)可以通过变量类型 fieldtype 和变量名 fieldname 来精确指定
2)可以通过 表示类中的任何成员变量
3)星号(“ * ”)可以匹配类中的任何成员变量和函数
成员函数
1)可以通过返回类型 returntype 、方法名 methodname 和参数类型 argumenttype 来唯一限定,
2) 可以通过 来表示类中的任何成员函数,
3)星号(“ * ”)可以匹配类中的任何成员函数
构造函数
1)可以用 加上构造函数的参数来指定。
成员函数名、成员变量名
1)问号(“ ? ”)可以匹配一个任意字符
2)星号(“ * ”)可以匹配任意多个任意字符
成员变量类型、成员函数返回类型以及参数、构造函数参数类型
1) % :匹配任何原始类型,如 boolean 、 int 等,但不包括 void ;
2) ? :匹配一个任意字符,不包括句号;
3) * :匹配任意个任意字符,不包括句号;
4) ** :匹配任意个任意字符,包括句号;
5) * :匹配任意类型,包括原始类型和非原始类型,数组类型和非数组类型;
6) … :匹配任何数目个任何类型的参数。
另外还需要说明的是,在类名前、类中成员变量和成员函数名前,可以加上访问限定符(如 public 、 private 、 protected 等,修饰类、成员变量和成员函数的访问限定符各不相同),如果加上了访问限定符后,就表示要匹配的类、成员变量或成员函数的定义中必须包含这些限定符。
3、android中不能混淆的代码
proguard混淆是把混淆对象的名字进行修改,而有些对象的名字不能被修改,否则会报错,比如Activity类,修改后AndroidManifest中就找不到对应的Activityl了肯定要报错,还有反射也是。那么这里便总结下android开发中不能被混淆的代码
1) Android系统组件
即在AndroidManifest文件中注册的组件,如Activity,Service,BroadcastReceiver,ContentProvider,Application等。这些组件通过字符串的形式告知系统,当被调用的时候,如果这些类名被改变了,那么系统就会找不到它们。所以一般组件不进行混淆。
2)Resource文件的使用
例如各种资源的名字在xml已经固定使用,如果混淆也将找不到。所以一般R文件不混淆。
3)序列化的类
包括Parcelable和Serializable。如果混淆了可能UID等会错误,而且在反序列化的时候会找不到对应的名字。
4)自定义控件
如果自定义控件在xml中使用了,那么也不能混淆,否则找不到,当然如果在代码中用的话应该没问题。
5)本地方法
不能修改本地方法名,否则和so文件对应的方法就无法找到了。
6.枚举
系统需要处理枚举的固定方法。
7)用到反射的地方
因为反射需要通过名字去找变量和方法名,所以混淆后可能找不到。例如实际情况中的接口返回数据对应的实体类等等。
8)ILicensingService系列
ILicensingService系列(好像是aidl用的)和BackupAgentHelper备份相关的,不能进行混淆,不过这个东西一般没人用,所以不必在意。
9)annotations 注释
一般注释混淆其实可以混淆,但是混淆常会和反射相关联,所以还是不要混淆的好。
10)android.preference.Preference
此Preference可不是SharedPrefrences(但是有点关联),这个Preference也是一个组件,所以不能混淆。
11)第三方包一般不混淆
首先,第三方包没必要混淆,既然是第三方的,那么一般都是公开的,也就没有必要保护。其次,第三方包可能自己已经混淆过了,不用我们动手了。最后,第三方包里面可能用到了上述的这些不能混淆的点,如果我们不了解它,擅自去混淆,可能导致不能正常运行的结果。
12)WebView中Java和Javascript互调的方法不能混淆
因为它们都是通过名字去互相调用,如果名字变了就会找不到对应的方法。
13)某些内部类的使用和泛型的使用需要避免混淆
到这里proguard知识,我想总结的就算是总结完了,可能看了这些都不能立即写出很好的proguard配置来,但是了解本博文知识点后会对怎么配置成竹在胸,也能知道什么对象是不能混淆的。关于proguard的配置,最好的还是在实际开发中多尝试,找感觉。
4、参考文献
本文的内容很多都来自于其它博客,都是稍作修改,感谢那些童鞋们的辛苦付出,把参考文献列在这里吧,也方便自己查看。
2、Android ProGuard混淆基本语法(和通用配置)