关于Proguard混淆的一份总结

ProGuard是最受欢迎的Java字节码优化器。 它使您的Java和Android应用程序缩小了90%,速度提高了20%。 ProGuard还通过模糊类,字段和方法的名称来提供对逆向工程的最小保护。本篇将从Android apk混淆和jar混淆两个方面进行展开。

我们通常说的proguard包括四个功能,shrinker(压缩), optimizer(优化),obfuscator(混淆),preverifier(预校验)。这里附上官网的一张流程图:

关于Proguard混淆的一份总结_第1张图片

  1. shrink: 检测并移除没有用到的类,变量,方法和属性;
  2. optimize: 优化代码,非入口节点类会加上private/static/final, 没有用到的参数会被删除,一些方法可能会变成内联代码。
  3. obfuscate: 使用短又没有语义的名字重命名非入口类的类名,变量名,方法名。入口类的名字保持不变。
  4. preverify: 预校验代码是否符合Java1.6或者更高的规范

无论是给apk添加混淆也好还是给jar文件或者aar文件添加混淆也好,底层其实都是依赖proguard.jar这个jar包来实现的,这个jar包安卓开发者可以在Android SDK根目录\tools\proguard\lib下找到,其他开发者可以在Proguard官网找到下载方式,以及官方说明文档。

如果有Android SDK的同学可以在{ANDROID_SDK_ROOT}/tools/proguard/lib/目录下找到proguard.jar这个jar包。或者,也可以在{ANDROID_SDK_ROOT}/tools/proguard/bin目录下直接使用脚本执行命令。

proguard.jar的使用:

使用proguard.jar添加混淆大致分为三种方式,

  1. 直接执行命令:

    java -jar proguard.jar options ...

  2. 配置文件方式:
    我们也可以把proguard的参数写到一个配置文件中,比如说proguard.cfg。那我们的命令可以这样写:
    java -jar proguard.jar @proguard.cfg
  3. 配置文件与配置参数混用:
    java -jar proguard.jar @proguard.cfg -verbose

配置文件格式:

  • 配置文件中 # 放在行首,用来做注释;
  • 单词之间多余的空格或分隔符会被忽略;
  • 如果文件名包含空格或者其它特殊符号,应当用单引号或者双引号括起来;
  • 配置参数的顺序与混淆结果是没有关系的(不绝对,-injars和-outjars是有先后之分的)

一、Android Studio apk混淆

在AndroidStudio中借助SDK中自带的Proguard工具,只需要修改build.gradle中的一行配置即可为apk添加混淆。

关于Proguard混淆的一份总结_第2张图片

可以看到minifyEnabled默认为false,我们只需将该设置改为true即可在打release包时候自动加载"proguard-android.txt"和"proguard-rules.pro"文件中的混淆规则为apk添加混淆处理。“proguard-rules.pro”文件大家都很熟悉,就是创建项目时候as默认为我们生成的。"proguard-android.txt"其实是Android Studio为我们配置的一些有关Android的默认混淆配置,位于Android SDK根目录\tools\proguard\proguard-android.txt

关于Proguard混淆的一份总结_第3张图片

proguard-android.txt这里面是一些比较常规的不能被混淆的代码规则。我们可以找到该文件,看看里面都有什么:

关于Proguard混淆的一份总结_第4张图片

关于Proguard混淆的一份总结_第5张图片

里面一些很少用到的配置我都已经标注出来。可以看到里面添加了一些含有native方法的类、自定义View中的get、set方法、以及R类和带Keep注解的类的保护。

至此,打出的release包应该都是混淆过的,只不过除了默认的混淆规则外,我们没有为自己的apk添加自己的混淆规则,接下来我们看看如何给自己的apk添加混淆规则。

apk混淆规则的编写我大致认为分三个大的方面,Proguard的一些基础配置、Android项目的一些通用配置、根据自己需求定制的一些配置。

1.Proguard的一些基础配置:

-optimizationpasses 5      				  //代码混淆的压缩比例,值在0-7之间,Android一般推荐用5
-dontusemixedcaseclassnames				        //不使用大小写类名混淆(混淆后类名都为小写)
-dontskipnonpubliclibraryclasses 		        //指定不去忽略非公共的库的类
-dontskipnonpubliclibraryclassmembers 	        //指定不去忽略非公共的库的类的成员
-dontpreverify 							        //不做预校验的操作
-verbose                				        //混淆时显示详细日志
-dontoptimize           				        //关闭优化
-printmapping proguardMapping.txt 		        //生成原类名和混淆后的类名的映射文件
-dontwarn                                       //忽略警告
-keepparameternames                             //保证方法参数不被混淆
-keepattributes *Annotation*,InnerClasses 		//不混淆Annotation和内部类
-keepattributes Signature 						//不混淆泛型
-keepattributes SourceFile,LineNumberTable 		//抛出异常时保留代码行号	

当然这只是一些最基础的配置,大家可以根据自己的需要看后面的注释更改配置。

2.通用配置:

-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 * extends android.view.View

-keep class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}

#移除log类中的d、v调用处
-assumenosideeffects class android.util.Log {
    public static *** d(...);
    public static *** v(...);
}

-keep public class com.android.vending.licensing.ILicensingService
-keep class android.support.** {*;}

#避免混淆所有native的方法
-keepclasseswithmembernames class * {
    native ;
}
#保持Activity子类里面的参数类型为View的方法不被混淆,如被XML里面应用的onClick方法
-keepclassmembers class * extends android.app.Activity{
    public void *(android.view.View);
}

#保持枚举类型values()、以及valueOf(java.lang.String)成员不被混淆
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

//保持View的子类里面的set、get方法不被混淆(*代替任意字符)
-keep public class * extends android.view.View{
    *** get*();
    void set*(***);
    public (android.content.Context);
    public (android.content.Context, android.util.AttributeSet);
    public (android.content.Context, android.util.AttributeSet, int);
}

-keepclasseswithmembers class * {
    public (android.content.Context, android.util.AttributeSet);
    public (android.content.Context, android.util.AttributeSet, int);
}

#保持实现Parcelable接口的类里面的Creator成员不被混淆
-keep class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}

-keepclassmembers class * implements java.io.Serializable {
    *;
}
//#保持R类不被混淆
-keep class **.R$* {
 *;
}

-keepclassmembers class * {
    void *(**On*Event);
}

#不警告support包中不使用的引用
-dontwarn android.support.**
#保持使用了Keep注解的方法以及类不被混淆
-keep class android.support.annotation.Keep
-keep @android.support.annotation.Keep class * {*;}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep ;
}

#保持使用了Keep注解的成员域以及类不被混淆
-keepclasseswithmembers class * {
    @android.support.annotation.Keep ;
}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep (...);
}

#---------------------------------webview------------------------------------
-keepclassmembers class fqcn.of.javascript.interface.for.Webview {
   public *;
}
-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, jav.lang.String);
}

里面有一些和上面提到的Android Studio自带的混淆规则重复了,主要是考虑到有些人不使用as自带的proguard工具混淆(后面会讲其他方式)。

3.定制混淆规则

定制混淆规则这边又可以划分为更细致的四步:实体类、第三方库、与JS交互的类(如果有)、反射相关的类(如果有)

这里着重讲一下第三方库的情况,如果是使用gradle依赖方式导入的库,可以在库的GitHub主页或者库的官网找到相应的混淆规则加入即可,如果是通过jar包的形式导入的,就需要使用-libraryjars指令完成,这里以android-support-v4.jar为例:

-libraryjars   libs/android-support-v4.jar
-dontwarn android.support.v4.**    
-keep class android.support.v4.** { *; }  
-keep interface android.support.v4.app.** { *; }
#如果gradle报错的话,可以考虑注释掉-libraryjars libs/android-support-v4.jar这句。

其他三步其实都是通过-keep class 要保护的类所在的包.** { *; } 方式给相应类添加保护,所以编码时要养成良好的习惯,相同类型的class文件放置在相同的包下。下面会讲一下为jar包添加混淆,以及使用Proguardgui工具。

二、使用proguardgui添加混淆

proguardgui是proguard.jar的图形化工具,对paoguard.jar和混淆命令进行了封装,通过可视化的界面帮助我们方便的给apk或jar包添加混淆。

对应于proguard.jar的三种加混淆方式,proguardgui也对应有三种使用方法:

  1. 直接load配置文件,一路下一步,直到完成;
  2. 通过可视化界面一步一步配置最终添加混淆;
  3. load配置文件,在下一步时候修改一些配置文件中的设置,最终添加混淆

有Android SDK的同学可以通过AndroidSDK安装目录\tools\proguard\bin\proguardgui.bat(Windows)、AndroidSDK安装目录\tools\proguard\bin\proguardgui.sh(mac)方式打开该软件。没有的可以通过proguardgui下载地址进行下载。

上面提到,proguard.jar也好,AndroidStudio也好添加混淆都需要编写混淆规则(即配置文件),下面我们主要来学习一下proguard的语法相关知识:

为了结合proguardgui的使用方法,我们这边把规则分成和proguardgui执行步骤相同的六步来说:

1.加载配置文件:

关于Proguard混淆的一份总结_第6张图片

  • -include filename  需要读取的配置文件(简写@filename)
  • -basedirectory directory  为所有引用的相对路径指定一个根路径(在proguardgui中没有体现)

2.input/output options(输入输出配置):

关于Proguard混淆的一份总结_第7张图片

  • -injars classpath 指定输入的包,可以包括 jar, aar, war, ear, zip, apk或者文件目录。这些包或者目录下的class文件将被处理后写入到输出文件中。默认情况下非class文件会被原封不动的复制到输出文件中。
  • -outjars classpath 指定输出文件,类型包括 jar, aar, war, ear, zip, apk和 目录。(不要让输出文件覆盖任何一个输入文件!)
  • -libraryjars classpaath  不混淆指定的jar包(android 项目中一般不混淆引入的第三方类库)

3.shrinking options(压缩配置):

关于Proguard混淆的一份总结_第8张图片

  • -dontshrink 声明不压缩输入文件。默认情况下,除了-keep相关配置指定的类,所有其它没有被引用到的类都会被删除。每次optimizate操作之后,也会执行一次压缩操作,因为每次optimizate操作可能删除一部分不再需要的类。
  • -printusage [filename] 声明打印出那些被删除的元素。这个列表可能打印到标准输出流或者一个文件中。仅在shrink阶段有效。

4.keep配置(这一步在proguardgui中的表现跟第三步在一张图上):

  • -keep [,modifier, ...] class_specification 指定类和类的成员变量是入口节点,保护它们不被移除混淆。例如,对一个可执行jar包来说,需要保护main的入口类;对一个类库来说需要保护它的所有public元素。
例子:
-keep public class mypackage.MyMain {
    public static void main(java.lang.String[]);
}
  • -keepclassmembers [,modifier] class_specification 保护指定的成员变量不被移除、优化、混淆。例如,保护所有序列化的类的成员变量。
例子:
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    !static !transient ;
    !private ;
    !private ;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}
  • -keepclasseswithmembers [,modifier,...] class_specification 拥有指定成员的类将被保护,根据类成员确定一些将要被保护的类。例如保护所有含有main方法的类。
例子:
-keepclasseswithmembers public class * {
    public static void main(java.lang.String[]);
}
  • -keepnames class_specification 是 -keep,allowshrinking class_pecification 的简写。指定一些类名受到保护,前提是他们在shrink这一阶段没有被去掉。也就是说没有被入口节点直接或间接引用的类还是会被删除。仅在obfuscate阶段有效。
  • -keepclassmembernames class_specification 是-keepclasseswithmembers,allowshrinking class_specification的简写。与-keepclassmember相似。保护指定的类成员,前提是这些成员在shrink阶段没有被删除。仅在obfuscate阶段有效。
  • -keepclasseswithmembernames class_specification 是-keepclasseswithmembers,allowshrinking class_specification的简写。与-keepclasseswithmembers类似。保护指定的类,如果它们没有在shrink阶段被删除。仅在obfuscate阶段有效。

keep命令修饰符:

  • includedescriptorclasses  它是用来声明描述目标成员的元素也应当被保护。它在保护native方法时特别有效。因为它可以同时保证参数类型,返回类型不被混淆。保证最终的方法签名保持一致。
例子:
-keepclasseswithmembernames,includedescriptorclasses class * {
    native ;
}
-keepclasseswithmembernames是保护符合条件的含有native方法的类。附加的includedescriptorclasses是保证参数和返回类型的类同样不被混淆。这样就可以做到这些类的方法签名与调试时完全一致。
  • allowshrinking 指定对象可能会被压缩,即使他们被keep选项保留。如果所指定的对象不是必需的,则他们可能会被删出(在压缩步骤)。反之如果他们是必须的,则他们可能不会被优化或者混淆。
  • allowoptimization 指定对象可能会被优化,即使他们被keep选项保留。所指定对象可能会被改变(优化步骤),但可能不会被混淆或者删除。该修饰符只对实现异常要求有用。
  • allowobfuscation 指定对象可能会被混淆,即使他们被keep保留。所指定对象可能会被重命名,但可能不会被删除或者优化。该修饰符只对实现异常要求有用。

5.optimization options(优化配置):

关于Proguard混淆的一份总结_第9张图片

  • -dontoptimize 声明不优化输入文件。默认情况下,优化选项是开启的,并且所有的优化都是在字节码层进行的。

  • -allowaccessmodification 这项配置是设置是否允许改变作用域的。使用这项配置之后可以提高优化的效果。但是,如果你的代码是一个库的话,最好不要配置这个选项,因为它可能会导致一些private变量被改成public。

  • -mergeinterfacesaggressively 指定一些接口可能被合并,即使一些子类没有同时实现两个接口的方法。这种情况在java源码中是不允许存在的,但是在java字节码中是允许存在的。它的作用是通过合并接口减少类的数量,从而达到减少输出文件体积的效果。仅在optimize阶段有效。

  • -optimizations optimization_filter 更加细粒度地声明优化开启或者关闭。只在optimize这一阶段有效。这个选项的使用难度较高。

  • -optimizationpasses n 指定执行几次优化,默认情况下,只执行一次优化。执行多次优化可以提高优化的效果,但是,如果执行过一次优化之后没有效果,就会停止优化,剩下的设置次数不再执行。这个选项只在optimizate阶段有效

  • -assumenosideeffects class_specification 指定一些方法被删除也没有影响(尽管这些方法可能有返回值),在optimize阶段,如果确定这些方法的返回值没有使用,那么就会删除这些方法的调用。proguard会自动的分析你的代码,但不会分析处理类库中的代码。例如,可以指定System.currentTimeMillis(),这样在optimize阶段就会删除所有的它的调用。还可以用它来删除打印Log的调用。这条配置选项只在optimizate阶段有用。

例如:
# 删除代码中Log相关的代码
-assumenosideeffects class android.util.Log {
    public static int v(...);
    public static int i(...);
    public static int w(...);
    public static int d(...);
    public static int e(...);
}

6.obfuscation options(混淆配置):

关于Proguard混淆的一份总结_第10张图片

  • -dontobfuscate 声明不混淆。默认情况下,混淆是开启的。除了keep配置中声明的类,其它的类或者类的成员混淆后会改成简短随机的名字。
  • -printmapping [filename] 指定输出新旧元素名的对照表的文件。映射表会被输出到标准输出流或者是一个指定的文件。
    (这个文件在追踪异常的时候是有用的,在{android_sdk_home}/tools/proguard/lib目录下有一个retrace.jar文件。我们可以把混淆后的Stack Trace用这个工具处理一下,就会转变成容易阅读的类。所以,做app应用的同学每次发版本的时候都要把这个文件留下来,并标记清楚版本。这对线上版本的调试非常重要。)
  • -applymapping filename 指定重用一个已经写好了的map文件作为新旧元素名的映射。元素名已经存在在mapping文件中的元素,按照映射表重命名;没有存在到mapping文件的元素,重新赋一个新的名字。mapping文件可能引用到输入文件中的类和类库中的类。这里只允许设置一个mapping文件。仅在obfuscate阶段有效。
  • -obfuscationdictionary filename 指定一个文本文件用来生成混淆后的名字。默认情况下,混淆后的名字一般为a,b,c这种。通过使用-obfuscationdictionary配置的字典文件,可以使用一些非英文字符做为类名。成员变量名、方法名。字典文件中的空格,标点符号,重复的词,还有以'#'开头的行都会被忽略。需要注意的是添加了字典并不会显著提高混淆的效果,只不过是更不利于阅读。正常的编译器会自动处理他们,并且输出出来的jar包也可以轻易的换个字典再重新混淆一次。最有用的做法一般是选择已经在类文件中存在的字符串做字典,这样可以稍微压缩包的体积。
查找了字典文件的格式:一行一个单词,空行忽略,重复忽略
# 这里巧妙地使用java中的关键字作字典,混淆之后的代码更加不利于阅读
#
# This obfuscation dictionary contains reserved Java keywords. They can't
# be used in Java source files, but they can be used in compiled class files.
# Note that this hardly improves the obfuscation. Decent decompilers can
# automatically replace reserved keywords, and the effect can fairly simply be
# undone by obfuscating again with simpler names.
# Usage:
#     java -jar proguard.jar ..... -obfuscationdictionary keywords.txt
#
do
if
for
int
new
try
byte
case
char
else
goto
long
this
void
break
catch
class
const
final
float
short
super
throw
while
double
import
native
public
return
static
switch
throws
boolean
default
extends
finally
package
private
abstract
continue
strictfp
volatile
interface
protected
transient
implements
instanceof
synchronized
  • -classobfuscationdictionary filename 指定一个混淆类名的字典,字典的格式与-obfuscationdictionary相同
  • -packageobfuscationdictionary filename 指定一个混淆包名的字典,字典格式与-obfuscationdictionary相同
  • -overloadaggressively 混淆的时候大量使用重载,多个方法名使用同一个混淆名,但是他们的方法签名不同。这可以使包的体积减小一部分,也可以加大理解的难度。仅在混淆阶段有效。
  • -useuniqueclassmembernames 指定相同的混淆名对应相同的方法名,不同的混淆名对应不同的方法名。如果不设置这个选项,同一个类中将会有很多方法映射到相同的方法名。这项配置会稍微增加输出文件中的代码,但是它能够保证保存下来的mapping文件能够在随后的增量混淆中继续被遵守,避免重新命名。比如说,两个接口拥有同名方法和相同的签名。如果没有这个配置,在第一次打包混淆之后,他们两个方法可能会被赋予不同的混淆名。如果说下一次添加代码的时候有一个类同时实现了两个接口,那么混淆的时候必然会将两个混淆后的方法名统一起来。这样就必须要改混淆文件其中一处的配置,也就不能保证前后两次混淆的mapping文件一致了。(如果你只想保留一份mapping文件迭代更新的话,这项配置比较有用)
  • -dontusemixedcaseclassnames 指定在混淆的时候不使用大小写混用的类名。默认情况下,混淆后的类名可能同时包含大写字母和小写字母。这样生成jar包并没有什么问题。只有在大小写不敏感的系统(例如windows)上解压时,才会涉及到这个问题。因为大小写不区分,可能会导致部分文件在解压的时候相互覆盖。如果有在windows系统上解压输出包的需求的话,可以加上这个配置。
  • -keeppackagenames [package_filter] 声明不混淆指定的包名。 配置的过滤器是逗号隔开的一组包名。包名可以包含?,,*通配符,并且可以在前面加!否定符。
  • -flatternpackagehierarchy [packagename] 所有重新命名的包都重新打包,并把所有的类移动到packagename包下面。如果没有指定packagename或者packagename为"",那么所有的类都会被移动到根目录下
  • -repackageclasses [package_name] 所有重新命名过的类都重新打包,并把他们移动到指定的packagename目录下。如果没有指定packagename,同样把他们放到根目录下面。这项配置会覆盖-flatternpackagehierarchy的配置。它可以代码体积更小,并且更加难以理解。这个与废弃的配置-defaultpackage作用相同。(如果需要从目录中读取资源文件,移动包的位置可能会导致异常。如果出现问题,就不要用这个配置了。)
  • -keepattributes [attribute_filter]  指定受保护的属性,可以有一个或者多个-keepattributes配置项,每个配置项后面跟随的是Java虚拟机和proguard支持的attribute,两个属性之间用逗号分隔。属性名中可以包含*,**,?等通配符。也可以加!做前导符,将某个属性排除在外。当混淆一个类库的时候,至少要保持InnerClasses, Exceptions,  Signature属性。为了跟踪异常信息,需要保留SourceFile, LineNumberTable两个属性。如果代码中有用到注解,需要把Annotion的属性保留下来。
  • -keepparameternames 指定被保护的方法的参数类型和参数名不被混淆。这项配置在混淆一些类库的时候特别有用,因为根据IDE提示的参数名和参数类型,开发者可以根据他们的语义获得一些信息,这样的类库更友好。
  • -renamesourcefileattribute [string] 指定一个字符串常量设置到源文件的类的属性当中。这样就可以在-keepattributes配置中使用。
  • -adaptclassstrings [classfilter] 指定字符串常量如果与类名相同,也需要被混淆。如果没有加classfilter,所有的符合要求的字符串常量都会被混淆;如果带有classfilter,只有在匹配的类中的字符串常量才会受此影响。例如,在你的代码中有大量的类名对应的字符串的hard-code,并且不想保留他们的本名,那就可以利用这项配置完成。这项配置只在混淆阶段有效,但是在压缩/优化阶段,涉及到的类会自动保留下来。
  • -adaptresourcefilenames [file_filter] 如果资源文件与某类名同,那么混淆后资源文件被命名为与之对应的类的混淆名。不加file_filter的情况下,所有资源文件都受此影响;加了file_filter的情况下,只有匹配到的类受此影响。
  • -adaptresourcefilecontents [file_filter] 指定资源文件的中的类名随混淆后的名字更新。根据被混淆的名字的前后映射关系,更新文件中对应的包名和类名。同样,如果不配置file_filter,所有的资源文件都会受此影响;配置了filter之后,只有对应的资源文件才受此影响。

7.preverification options(预校验配置):

关于Proguard混淆的一份总结_第11张图片

  • -dontpreverify 声明不预校验即将执行的类。默认情况下,在类文件的编译版本为java micro版本或者大于1.6版本时,预校验是开启的。目标文件针对java6的情况下,预校验是可选的;针对java7的情况下,预校验是必须的,除非目标运行平台是Android平台,设置它可以节省一点点时间。
    目标为Java Micro版本的情况下,预校验是必须的。如果你声明了这项配置,你还需要加上下面一条配置。
  • -microedition 声明目标平台是java micro版本。预校验会根据这项配置加载合适的StackMap,而不是用标准的StackMap。
    -whyareyoukeeping class_specification 声明 打印为什么一个类或类的成员变量被保护。这对检查一个输出文件中的类的结果有帮助。
  • -target version 指定处理的class文件中java的目标版本。版本号是1.0, 1.1, 1.2, 1.3, 1.4, 1.5(或者5), 1.6(或者6), 1.7(或者7),1.8(或者8)之中的一个。默认情况下,class文件的版本号是不会变的。

8.一般配置(这一步在proguardgui中体现和第七步一张图):

  • -skipnonpubliclibraryclasses 指定读取引用库文件的时候跳过非public类。这样做可以提高处理速度并节省内存。一般情况下非public在应用内是引用不到的,跳过它们也没什么关系。但是,在一些java类库中中出现了public类继承非public类的情况,这样就不能用这个选项了。这种情况下,会打印一个警告出来,提示找不到类。
  • -dontskipnonpubliclibraryclasses 跟上面的参数相对。版本4.5以上,这个是默认的选项。
  • -dontskipnonpubliclibraryclassmembers 指定不忽略库类库中的非public成员(成员变量和方法)。默认情况下,proguard在读取库文件的时候会自动忽略这些类的成员,因为这些非public成员不会被源代码引用到。但有时候他们是可以被引用到的。比如说,源代码中与库文件用同一个包名,那么源代码就可以访问包作用域的变量。在这些情况下,为了引用一致,不被混淆,就需要指定不跳过这些类。
  • -keepdirectories directory_filter 指定输出jar包中需要保留的目录名。为了减少输出文件的体积,默认情况下所有的目录都会被删除(个人这里不是很理解,猜测意思是默认所有目录都会被混淆吧)。但是如果你的代码中有需要从目录中寻找文件的逻辑,那你就需要保持目录名一致。这项配置后面不加过滤器的时候,所有目录都会被保留。加了过滤器之后,只有过滤器匹配的目录才会被保留。
  • -forceprocessing 尽管输出文件已经是最新的,还是强制进行处理一次。
  • -printseeds [filename] 打印出指定keep保护的类的详细列表,可以帮助我们查看需要保护的类有没有被保护到
  • -verbose  声明在处理过程中输出更多信息。添加这项配置之后,如果处理过程中出现异常,会输出整个StackTrace而不是一条简单的异常说明。
  • -dontnote [class_filter]   声明不输出那些潜在的错误和缺失,比如说错别字或者重要的配置缺失。配置中的class_filter是一串正则表达式,混淆过程中不会输出被匹配到的类相关的内容。
  • -dontwarn [class_filter]   声明不输出那些未找到的引用和一些错误,继续混淆。配置中的class_filter是一串正则表达式,被匹配到的类名相关的警告都不会被输出出来。
  • -ignorewarnings   输出所有找不到引用和一些其它错误的警告,但是继续执行处理过程。不处理警告有些危险,所以在清楚配置的具体作用的时候再使用。
  • -printconfiguration [filename]   输出整个处理过程中的所有配置参数,包括文件中的参数和命令行中的参数。可以不加文件名在标准输出流中输出,也可以指定文件名输出到文件中。它在调试的时候比较有用。

经过以上步骤,proguard的配置文件就已经完成了。下面对于配置文件中的类和方法的匹配规则做一下介绍(即通配符使用)。

匹配规则和通配符相关内容

  • extend与implements 关键字是用来限制类的范围的。他们目前是等价的,用来匹配某些类的子类。需要注意的是,这个指定的类并不包括在匹配结果中,如果想要该类也被匹配到,就需要额外声明一项配置。
  • 类或者类成员的修饰符也是匹配类的限制条件。通过修饰符限制,可以缩小匹配的范围。修饰符组合也是可以的,就像java中的public static一样,但是不能冲突, 比如public private。
  • 成员变量和成员方法的匹配形式与java非常像,只是方法的参数不带参数名。
  • class 关键字可以匹配class类或interface类,但是interface关键字只能匹配interface类,enum关键字只能匹配enum类。在interface或enum关键字前加一个!,可以表示非这种类型的类。
  • classname 必须写全名,比如java.lang.String。内部类用$间隔,例如,java.lang.Thread$State。类名可以用含有下面这些通配符的正则表达式匹配:
*

匹配任意长度字符,但不含包名分隔符(.)。比如说我们的完整类名是com.example.test.MyActivity,使用com.*,或者com.exmaple.*都是无法匹配的,因为*无法匹配包名中的分隔符,正确的匹配方式是com.exmaple.*.*,或者com.exmaple.test.*,这些都是可以的。但如果你不写任何其它内容,只有一个*,那就表示匹配所有的东西。此外,mypackage.*Test*可以匹配到mypackage.Test和mypackage.YourTestApplication但是不能匹配mypackage.mysubpackage.MyTest。一种常用的写法mypackage.*就是匹配mypackage下的所有子文件。

** 匹配任意长度字符,并且包含包名分隔符(.)。比如proguard-android.txt中使用的-dontwarn android.support.**就可以匹配android.support包下的所有内容,包括任意长度的子包。
*** 匹配任意参数类型。比如void set*(***)就能匹配任意传入的参数类型,*** get*()就能匹配任意返回值的类型。
... 匹配任意长度的任意类型参数。比如void test(…)就能匹配任意void test(String a)或者是void test(int a, String b)这些方法。
@ 符号匹配那些注解标志的类或类成员,它的通配符形式与classname的形式一样。
? 匹配类名中的一个字符,不包括文件分隔符。例如,mypackage.Test?可以匹配mypackage.Test1,mypackage.Test2,但不能匹配mypackage.Test12。
% 匹配java中的初始类型(int, boolean, long, float,double等)

变量名和方法名可以使用的通配符:

  •   匹配类中的所有字段
  •   匹配类中的所有方法
  •   匹配类中的所有构造函数

注意

  • 上述通配符并不能设置返回类型,并且只有方法带有参数
  • 需要注意的是?, *, **不能够匹配初始类型和数组。***可以匹配到数组。

 

参考资料:

https://www.guardsquare.com/en/products/proguard/manual/usage

https://blog.csdn.net/guolin_blog/article/details/50451259

 

你可能感兴趣的:(android)