proguard是一个工具,常用于代码混淆,但是其作用不止是混淆,而是压缩(删除未使用的类方法字段)、优化(对字节码进行优化)、混淆(名称无意义化)、预检(确保字节码能够可执行)。
在android studio中对proguard的使用主要在于两个文件:module的gradle文件以及proguard-rules.pro文件。
首先来看module级的gradle中的相关代码:
buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } }
release表示正式版本的配置,与之对应的是debug版本,所以可以在release下面用debug另起一个大括号配置debug版本。minifyEnabled表示是否开启执行proguard。接下来有两个文件,第一个是JDK中自带的Proguard配置文件,内含一些默认的配置。第二个我们自己的proguard配置文件,在默认配置之外还有需要的话就写在这个文件里,比如保证一些文件不被混淆,或者对是否压缩进行设置。
看看默认的proguard文件里都配置了什么:
# This is a configuration file for ProGuard. # http://proguard.sourceforge.net/index.html#manual/usage.html # 包名不混合大小写 -dontusemixedcaseclassnames # 不忽略非公共的库类 -dontskipnonpubliclibraryclasses # 输出混淆日志 -verbose # Optimization is turned off by default. Dex does not like code run # through the ProGuard optimize and preverify steps (and performs some # of these optimizations on its own). # 不进行优化 -dontoptimize # 不进行预检验 -dontpreverify # Note that if you want to enable optimization, you cannot just # include optimization flags in your own project configuration file; # instead you will need to point to the # "proguard-android-optimize.txt" file instead of this one from your # project.properties file. # 不混淆注解(注解不能被混淆) -keepattributes *Annotation* # 不混淆指定类 -keep public class com.google.vending.licensing.ILicensingService -keep public class com.android.vending.licensing.ILicensingService # For native methods, see http://proguard.sourceforge.net/manual/examples.html#native # 不混淆native的方法 -keepclasseswithmembernames class * { native <methods>; } # keep setters in Views so that animations can still work. # see http://proguard.sourceforge.net/manual/examples.html#beans # 不混淆继承View的所有类的set和get方法 -keepclassmembers public class * extends android.view.View { void set*(***); *** get*(); } # We want to keep methods in Activity that could be used in the XML attribute onClick # 不混淆继承Activity的所有类的中的参数类型为View的方法 -keepclassmembers class * extends android.app.Activity { public void *(android.view.View); } # For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations # 不混淆枚举类型的values和valueOf方法 -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); } # 不混淆继承Parcelable的所有类的CREATOR -keepclassmembers class * implements android.os.Parcelable { public static final android.os.Parcelable$Creator CREATOR; } # 不混淆R类中所有static字段 -keepclassmembers class **.R$* { public static <fields>; } # The support library contains references to newer platform versions. # Don't warn about those in case this app is linking against an older # platform version. We know about them, and they are safe. # 忽略兼容库的所有警告 -dontwarn android.support.** # Understand the @Keep support annotation. # 不混淆指定的类及其类成员 -keep class android.support.annotation.Keep # 不混淆使用注解的类及其类成员 -keep @android.support.annotation.Keep class * {*;} # 不混淆所有类及其类成员中的使用注解的方法 -keepclasseswithmembers class * { @android.support.annotation.Keep <methods>; } # 不混淆所有类及其类成员中的使用注解的字段 -keepclasseswithmembers class * { @android.support.annotation.Keep <fields>; } # 不混淆所有类及其类成员中的使用注解的初始化方法 -keepclasseswithmembers class * { @android.support.annotation.Keep <init>(...); }
这是默认的配置,注解使用反射,反射传入完整名称,所以不能混淆。接下来我们可以在Proguard-rules.pro中编写自己所需的配置。打开项目可以看到这个文件里只有一些注释,没有实际代码。
语法就是-开头表示一条配置
-include {filename} 从给定的文件中读取配置参数 -basedirectory {directoryname} 指定基础目录为以后相对的档案名称 -injars {class_path} 指定要处理的应用程序jar,war,ear和目录 -outjars {class_path} 指定处理完后要输出的jar,war,ear和目录的名称 -libraryjars {classpath} 指定要处理的应用程序jar,war,ear和目录所需要的程序库文件 -dontskipnonpubliclibraryclasses 指定不去忽略非公共的库类。 -dontskipnonpubliclibraryclassmembers 指定不去忽略包可见的库类的成员。 保留选项 -keep {Modifier} {class_specification} 保护指定的类文件和类的成员 -keepclassmembers {modifier} {class_specification} 保护指定类的成员,如果此类受到保护他们会保护的更好 -keepclasseswithmembers {class_specification} 保护指定的类和类的成员,但条件是所有指定的类和类成员是要存在。 -keepnames {class_specification} 保护指定的类和类的成员的名称(如果他们不会压缩步骤中删除) -keepclassmembernames {class_specification} 保护指定的类的成员的名称(如果他们不会压缩步骤中删除) -keepclasseswithmembernames {class_specification} 保护指定的类和类的成员的名称,如果所有指定的类成员出席(在压缩步骤之后) -printseeds {filename} 列出类和类的成员-keep选项的清单,标准输出到给定的文件 压缩 -dontshrink 不压缩输入的类文件 -printusage {filename} -whyareyoukeeping {class_specification} 优化 -dontoptimize 不优化输入的类文件 -assumenosideeffects {class_specification} 优化时假设指定的方法,没有任何副作用 -allowaccessmodification 优化时允许访问并修改有修饰符的类和类的成员 混淆 -dontobfuscate 不混淆输入的类文件 -printmapping {filename} -applymapping {filename} 重用映射增加混淆 -obfuscationdictionary {filename} 使用给定文件中的关键字作为要混淆方法的名称 -overloadaggressively 混淆时应用侵入式重载 -useuniqueclassmembernames 确定统一的混淆类的成员名称来增加混淆 -flattenpackagehierarchy {package_name} 重新包装所有重命名的包并放在给定的单一包中 -repackageclass {package_name} 重新包装所有重命名的类文件中放在给定的单一包中 -dontusemixedcaseclassnames 混淆时不会产生形形色色的类名 -keepattributes {attribute_name,...} 保护给定的可选属性,例如LineNumberTable, LocalVariableTable, SourceFile, Deprecated, Synthetic, Signature, and InnerClasses. -renamesourcefileattribute {string} 设置源文件中给定的字符串常量
通配符:
- field 匹配类中的所有字段
- method 匹配类中的所有方法
- init 匹配类中的所有的构造函数
*
匹配任意长度字符,但不含包名分隔符(.),比如说我们的完整类名是com.example.test.MainActivtiy,使用com.*
,或者com.example.*
,都是无法匹配的,因为*
无法匹配包名中的分隔符,正确的匹配方式是com.example.*
.*
,或者是com.example.test.*
,这些都是可以的。但如果你不写任何其他内容,只有一个*
,那就表示匹配所有的东西**
匹配任意长度字符,并且含包名分隔符(.),比如android.support.**
就可以匹配android.support包下所有的内容,包括任意长度的子包.***
匹配任意参数类型,比如void set(***
)就能匹配任意传入的参数类型,***
get*
()就能匹配任意返回值的类型- … 匹配任意长度的任意参数类型,比如void set(…)就能匹配任意的void set(String a)或者是void set(String a,int b)等方法