介绍
Proguard是一款Java工具,具有收缩,优化,混淆,预验证功能。
当构建版本时会连续执行这四个步骤:
1.收缩(shrinker): 检测并删除无用的类,函数,属性,和域,以达到减小包体积的目的。
2.优化(optimizer):优化字节码和删除无用的指令。
3.混淆(obfuscator):把1和2处理后的包,再把类名和方法名以及域名换成无意义的且简短的字母来代替,如a,b。
4.预验证(preverifier):预先验证经过1,2,3处理后的代码是否符合JAVA 6和Java Micro Edition的规则要求。
以上四个步骤主要体现在配置文件上,首先在project.properties文件中添加一句proguard.config=proguard.cfg,并新建proguard.cfg文件,接着在proguard.cfg中写配置就可以了。
实例
下面介绍Android工程proguard官方配置写法。
-injars bin/classes #输入jars
-injars libs #输入jars
-outjars bin/classes-processed.jar #输出jars
#指明Android包位置,不过有可能已经指明,会混淆报错,报错就注释掉
#-libraryjars /usr/local/java/android-sdk/platforms/android-9/android.jar
#打印混淆日志,方便以后跟踪
-printmapping bin/classes-processed.map
-dontpreverify #不要预检验
-repackageclasses '' #把因混淆重命名的类重新打包,放到同一个包下
-allowaccessmodification #允许在处理过程中扩大类的访问权限
-optimizations !code/simplification/arithmetic #优化的算法
#保持固定的资源文件,行号以及签名,方便后续问题跟踪和包验证
-renamesourcefileattribute SourceFile
-keepattributes SourceFile,LineNumberTable,Signature
#保证注解不会被混淆,因为可能会在RemoteViews中用到
-keepattributes *Annotation*
#保证Activity,Application,Service,BroadcastReceiver,ContentProvider不会被混淆,因为他们会在AndroidManifest.xml中用到,#AndroidManifest.xml不会被混淆,所以他们也不能被混淆。后则混淆后的名称都是无意义的,AndroidManifest.xml会找不到这些类的。
-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.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*(...);
}
#保持所有Context构造函数
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
#保持onClick处理
-keepclassmembers class * extends android.content.Context {
public void *(android.view.View);
public void *(android.view.MenuItem);
}
#R文件不能混淆,要靠它来取ID,下面的是官方的写法,但这样写会有问题,如果引用的jar包中包含布局文件,他们会通过反射取R文件,所以R文件的类名不能被混淆
#-keepclassmembers class **.R$* {
# public static <fields>;
#}
-keep class **.R$* {
public static <fields>;
}
#保持Javascript接口
-keepclassmembers class * {
@android.webkit.JavascriptInterface <methods>;
}
#如果你用到了Google的证书校验,请把以下加上,如果没用到加上也行
-keep public interface com.android.vending.licensing.ILicensingService
-dontnote com.android.vending.licensing.ILicensingService
#不让android-support-v*.jar警告
-dontwarn android.support.**
#保护本地代码
-keepclasseswithmembernames,includedescriptorclasses class * {
native <methods>;
}
#保持所有枚举类都要用到的静态方法,下面是官方写法
#-keepclassmembers,allowoptimization enum * {
# public static **[] values();
# public static ** valueOf(java.lang.String);
#}
#但是这样,如果枚举用作序列化传递,会出现问题,所以枚举最好不要混淆
-keep enum * {*;}
#Parcelable 的静态构造因为外界可能通过反射取值,下面是官方写法
#-keepclassmembers class * implements android.os.Parcelable {
# static android.os.Parcelable$Creator CREATOR;
#}
#为了防止奇奇怪怪的问题,最好也不要混淆
-keep class * implements android.os.Parcelable{*;}
#序列化保持,下面是官方写法
#-keepclassmembers class * implements java.io.Serializable {
# static final long serialVersionUID;
# static final java.io.ObjectStreamField[] serialPersistentFields;
# private void writeObject(java.io.ObjectOutputStream);
# private void readObject(java.io.ObjectInputStream);
# java.lang.Object writeReplace();
# java.lang.Object readResolve();
#}
#同Parcelable,应当不混淆
-keep class * implements java.io.Serializable {*;}
以上的混淆配置已经基本上可以满足项目需要,如果没有引用第三方包的话。
那么问题来了,如果引用第三方包怎么办(挖掘机技术哪家强)?
答案是——不怎么办,根据情况而定。
接下来要说对于jar包特殊处理,不过要提前了解一些东西, 也是前面看着晕晕乎乎东西的解释:
详解
1.keep
1.1 keep简介
Keep | 收缩时不被移出,混淆时不被重命名 | 混淆时不被重命名 |
---|---|---|
保持类和类的成员 | -keep |
-keepnames |
仅保持类的成员 | -keepclassmembers |
-keepclassmembernames |
保持类和类的成员,如果类的成员被展现出来的话 | -keepclasseswithmembers |
-keepclasseswithmembernames |
如果你实在搞不清用哪一个,就直接用-keep,因为他保证了类和类的成员都是未混淆之前的样子,宁愿不杀,也不要杀错~~
1.2 keep保留
这里的keep,是指这些keep的代码不会经过收缩,优化和混淆步骤,当然如果你想保留某一步骤(比如你只想不混淆,还想收缩),可以额外加上allowshrinking(允许收缩),allowoptimization(允许优化),allowobfuscation(允许混淆)
1.3 keep参数介绍
如果想keep特定的类或方法,可用下面这个模版匹配
[@annotationtype] [[!]public|final|abstract|@ ...] [!]interface|class|enum classname是不是看晕了?
[extends|implements [@annotationtype] classname]
[{
[@annotationtype] [[!]public|private|protected|static|volatile|transient ...] <fields> |
(fieldtype fieldname);
[@annotationtype] [[!]public|private|protected|static|synchronized|native|abstract|strictfp ...] <methods> |
<init>(argumenttype,...) |
classname(argumenttype,...) |
(returntype methodname(argumenttype,...));
[@annotationtype] [[!]public|private|protected|static ... ] *;
...
}]
再和上面的实例结合看一看,是不是又有点明白?下面解释一下
1.3.1 字段
@annotationtype 注解,注解是什么,就不在这里说了
public|private|protected|static|volatile|transient 修饰符,这个也不说了~~
interface|class|enum 类型
1.3.2 符号
[ ] 选项,可有可无
| 或,比如interface|class|enum 只能在这个三个中选一个
!取反,-keep class 就是保持class,如果-keep !class就是保持class之外的类型
匹配符:
? 匹配任意单个字符,包的分隔符除外,如"mypackage.Test?
" 可以匹配 "mypackage.Test1
" 和 "mypackage.Test2
", 但不能匹配 "mypackage.Test12
".*
* 除包分隔符之外的任意多的字符串,例如, "mypackage.*Test*
" 匹配 "mypackage.Test
" 和"mypackage.YourTestApplication
", 但是不能匹配
"mypackage.mysubpackage.MyTest
"。总的来讲, "mypackage.*
" 匹配 "mypackage
"包中的所有类, 但是不能匹配它子包中的类。
** 匹配所有任意数量字符,如"mypackage.**
" 匹配mypackage的所有子包和子类
1.3.3 方法
<init> |
匹配所有构造函数 |
<fields> |
匹配所有域 |
<methods> |
匹配所有方法 |
* |
匹配所有方法和域 |
注:对此仍有很多细节未进行说明,比如函数的具体匹配等,详情请参考文档:https://stuff.mit.edu/afs/sipb/project/android/sdk/android-sdk-linux/tools/proguard/docs/index.html#manual/usage.html
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
第三方jar导入
现在言归正传,说如如何引用第三方jar包
注:如果是library库引用,不用管~~~~~
1.有的第三方jar包会报出warning警告,不用管,加上-dontwarn关键字就行。
e.g. fastjson-1.1.41.jar 用于json数据解析,混淆时会报Waring,脚本中加上-dontwarn com.alibaba.fastjson.**即可
2.所有的jar包,请老老实实的加上-keep class 包名+**{*;},比如我有添加了apache的一个jar包,我要写上-keep class org.apache.**{*;},注:**指包含子包中的内容,{*;}指类中的所有方法。
3.-keep class并不是绝对的,但为了节省一个个甄别的时间,都keep了也没多大关系。如果有必要的话,interface也要keep,这种情况比较少见
4.有些jar包的特殊要求:如一些jar包要对某些类反射取值,这时候要对这些类进行保持。如fastjson,是对实体进行反射解析的,所以要对fastjson使用的实体进行keep
小技巧:一般混淆方法,都会在jar包提供方的官方网站上提及,仔细找找噢~
注:以上解说仅适用于proguard 5.0以上,如果低于次版本,请移步官方升级https://stuff.mit.edu/afs/sipb/project/android/sdk/android-sdk-linux/tools/proguard/docs/index.html#downloads.html,升级方法也很简单,把下载的包解压出来,把SDK(路径:\sdk\tools\proguard)中的proguard文件夹替换掉就可以了。
更详细的内容请参考:https://stuff.mit.edu/afs/sipb/project/android/sdk/android-sdk-linux/tools/proguard/docs/index.html#