Proguard特性
压缩:Java源代码通常被编译为字节码,虽然字节码比源代码更简洁,但它本身仍然会包含很多无用的代码。Proguard的压缩功能通过分析字节码,能够检测并移除没有使用的类,字段,方法和属性;优化:优化Java字节码,同时移除没有使用到的指令;混淆:使用无意义的简短字母组合对类名、字段名、方法名进行重命名;预检验:对上述处理后的代码进行预检验;
混淆配置
buildTypes {
release {
minifyEnabled
true //
true表示使能Proguard
proguardFiles getDefaultProguardFile(
'proguard-android.txt'),
'proguard-rules.pro'
}
}
混淆文件的规则分类
公共的混淆规则:每个APP都通用的,主要是对Proguard的基本配置,以及Android SDK中API设置的规则,例如Activity、Parcelable等;App特有的混淆规则:根据APP自身的特点进行设置,例如某些类会被反射调用,如果被混淆,那么反射就找不到类了;第三方函数库或者SDK的混淆规则:如果APP引入了第三方开源函数库或者SDK,那么需要查看这些函数库或者SDK的使用说明,将需要去混淆的地方加上去;
混淆文件编写
#代码迭代优化的次数,取值范围0 - 7,默认5
-
optimizationpasses 5
#混淆时不使用大小写混合的方式,这样混淆后都是小写字母的组合
-
dontusemixedcaseclassnames
#混淆时不做欲校验,欲校验是Proguard四大功能之一,在Android中一般不需要欲校验,这样可以加快混淆的速度
-
dontpreverify
#混淆时记录日志,同时会生成映射文件,Android Studio中,生成的默认映射文件是 'build/outputs/mapping/release/mapping.txt'
-
verbose
#生成指定的映射文件的路径和名称
-
printmapping build/outputs/mapping/release/mymapping.txt
#混淆时所采用的算法,参数是Google官方推荐的过滤器算法
-
optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
#如果项目中用到了注解,需要保留注解属性
-
keepattributes *Annotation*
#不混淆泛型
-
keepattributes Signature
#保留代码行号,这在混淆后代码运行中抛出异常信息时,有利于定位出问题的代码
-
keepattributes SourceFile,LineNumberTable
#保持Android SDK相关API类不被混淆
-
keep public class * extends android.app.Activity
-
keep public class * extends android.app.Application
-
keep public class * extends android.app.Service
-
keep public class * extends android.content.BroadcastReceiver
-
keep public class * extends android.content.ContentProvider
-
keep public class * extends android.app.backup.BackupAgentHelper
-
keep public class * extends android.preference.Preference
-
keep public class com.android.vending.licensing.ILicensingService
#保留R类
-
keep class **.R$*{
*;
}
#保留native方法不被混淆
-
keepclasseswithmembernames class * {
native<methods>;
}
#保持自定义控件类不被混淆
-
keepclasseswithmembers class * {
public <init>(android.content.Context,android.util.AttributeSet);
}
-
keepclasseswithmembers class * {
public <init>(android.content.Context,android.util.AttributeSet,int);
}
#保持Activity中参数是View类型的参数,保证在Layout XML文件配置的 onClick 属性的值能够正常调用到
-
keepclassmembers class * extends android.app.Activity{
public void *(android.view.View);
}
#保持枚举类不被混淆
-
keepclassmembers enum * {
public static ** [] values();
public static ** valueOf(java.lang.String);
}
#保持Parcelable不被混淆
-
keep class * implements android.os.Parcelable{
public static final android.os.Parcelable$Creator *;
}
#保持Serializable序列化类相关方法和字段不被混淆
-
keepclassmembers class * implements java.io.Serializable{
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient <fields>;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
#保持自定义控件不被混淆
-
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 set*(...);
*** get*();
}
#引入各个开源库需要增加的混淆
针对App的量身定制
假设我们创建一个项目,包名和结构名如下:
1、保留实体类和成员不被混淆 对于实体类,要保留它们的set和get方法,对于boolean型get方法,有人喜欢命名为isXXX方式,所以不要遗漏。一种好的做法是把所有实体都放在一个包下管理,这样只写一次混淆就够了,避免在别的包中新增实体而忘记保留,代码在混淆后因为找不到实体类而崩溃。
-keep
public class heyha.nj.com.heyha.entity.** {
public void set*(***);
public ***
get*();
public ***
is*();
}
2、内嵌类 内嵌类经常被混淆,结果在调用的时候为空就崩溃了。最好的解决办法就是把这个内嵌类拿出来,单独成类。如果一定要内置,那这个类就必须在混淆时保留,例如保留heyha.nj.com.heyha.activity包下MainActivity的所有内嵌类,代码如下:$是用于分割内嵌类与其母体的标识。
-keep class
com.heyha.nj.heyha.activity.MainActivity$* {
*
}
3、对WebView的处理 如果用到了webview的复杂操作,需要添加如下代码:
-keepclassmembers
class * extends android.webkit.webViewClient {
public void *(android.webkit.WebView,java.lang.String,android.graphics.Bitmap);
public boolean *(android.webkit.WebView,java.lang.String);
}
-keepclassmembers
class * extends android.webkit.webViewClient {
public void *(android.webkit.webView,java.lang.String);
}
4、对JavaScript的处理 App与HTML5页面的JavaScript进行交互,如下所示:
class JSInteface1 {
@JavascriptInterface
public void callAndroidMethod(
int a,
float b, String c,
boolean d){
if(d){
String strMessage =
"-" + (a +
1) +
"-" + (b +
1) +
"-" + c +
"-" + d;
new AlertDialog.Builder(MainActivity.
this).setTitle(
"title")
.setMessage(strMessage).show();
}
}
}
JSInteface1是MainActivity的子类,所以保留指令写法如下,而且需要对所有使用的地方设置保留指令
-keepclassmembers class
com.heyha.nj.heyha.activity.MainActivity$JSInteface1 {
<methods>
}
5、处理反射 在混淆过程中,无论是Class.forName(“SomeClass”),还是SomeClass.class,SomeClass这个类的名称都会被混淆,因此,在混淆文件中,需要保留这个类。此外,还有如下方法:
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")
6、自定义view的解决方案 凡是在layout目录中的xml布局文件中配置的自定义View,都不能进行混淆。为此要遍历layout下所有的xml布局文件,找到那些自定义view,然后确认其是否在proguard文件中保留了。