Android Proguard混淆详解

介绍

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#




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值