Android 打包之混淆

Dex打包关于65536的问题

由于DEX文件格式限制,一个DEX文件中的method个数采用使用原生类型short来索引文件的方法,也就是4个字节共计最多表达65536个method,field/class个数也均有此限制,对于DEX文件,则是将工程所需要全部class文件合并压缩到一个DEX文件期间,也就是Android打包的DEX过程中,单个DEX文件可被引用的方法总数(自己开发的代码以及所引用的Android框架、类库的代码)被限制为66536。

Android是怎么通过R文件找到真正的资源文件

aapt工具对每个资源文件都生成了唯一的ID,这些ID保存在R.java文件中。资源ID是一个4字节的的无符号证书,在R.java文件中用16进程表示。其中,最高的1字节表示Package ID,次高1个字节表示Type ID,最低2字节表示Entry ID。还生成一个文件resources.arsc,相当于一个资源索引表,也可以理解成一个map,map的key是资源ID,value是资源在apk文件中的路径。

混淆 ProGuard

因为Java代码是非常容易反编码的,况且Android开发的应用程序是用Java代码写的,为了很好的保护Java源代码,我们需要对编译好的后的class文件进行混淆。
ProGuard是一个混淆代码的开源项目,它的主要作用是混淆代码,但是其实它主要有4个功能如下:

  1. 压缩(Shrink):检测并移除代码中无用的类、字段、方法和特性(Attribute)
  2. 优化(Optimize):字节码进行优化,移除无用的指令。
  3. 混淆(Obfuscate):使用a、b、c、d这样简短而无意义的名称,对垒、字段和方法进行重命名。
  4. 预检测(Preveirfy):在Java平台对处理后的代码进行预检测,确保加载class文件是可执行的。

ProGurad的使用

  1. 开启混淆
 buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
}
  1. 混淆过程
    ProGuard由shrink、optimize、obfuscate和preverify四个步骤组成,每个步骤都是可选的,需要哪些步骤都可以在脚本 proguard-rules.pro 中配置。
  • 压缩(Shrink):默认开启,侦测并移除代码中无用的类、字段、方法和特性,减少应用体积,并且会在优化动作执行之后再次执行(因为优化后可能会再次暴露一些未使用的类和成员)。
    -dontshrink 关闭混淆
  • 优化(Optimize):默认开启,分析和优化字节码,让应用运行的更快。
    -dontoptimize 关闭优化,默认混淆配置文件开始
    -optimizationpasses n 表示proguard对代码进行迭代优化的次数,Android一般为5
  • 混淆(Obfuscate):默认开启,使用a、b、c、d这样简短而无意义的名称,对类、字段和方法进行重命名,增大反编译难度。
    -dontobfuscate 关闭混淆
  • 预检(Preverify):在java平台上对处理后的代码进行预检。

上面三个步骤使代码大小更小、更高效,也更难被逆向工程。


混淆过程

这里引入到一个Entry Point(入口点) 概念,Entry Point是在ProGurad过程中不会被处理的类或方法。在压缩的过程中,ProGuard会从上述的Entry Point开始递归遍历,搜索哪些类和类的成员在使用,对于没有使用的类和类的成员,就会在压缩端被丢弃,在接下来的优化过程中,那些非Entry Point类、方法都会被设置为private、static或final,不实用的参数会被移除,此外,有些方法会被标记为内联的,在混淆的不会走中,ProGuard会对非Entry Point的类和方法进行重命名。

  1. ProGuard的工具目录


    工具目录
  • bin
    bin目录中包含了几个bat和shell脚本,通过这些脚本可以直接执行proguard.jar,proguardgui.jar和retrace.jar。如果将bin目录添加到环境变量中,就可以直接在命令行中执行progurad,proguardgui和retrace命令了,避免每次都要输入 java -jar +
  • lib
    lib目录包含了Proguard工具对应的jar文件,其中又包含三个文件:proguard.jar,proguardgui.jar和retrace.jar。
    • proguard.jar:Progurad的四项核心功能shrink.optimize,obfuscate和preverify的执行都是由progurad.jar来完成,不过proguard.jar只能通过命令行的方式来使用。
    • proguardgui.jar:是Proguard提供的一个图形界面工具,通过proguardgui.jar可以方便的查看和编辑Proguard配置,以及调用proguard.jar来执行一次优化过程。
    • retrace.jar主要是在debug时使用。混淆之后的jar文件执行过程如果出现异常,生成的异常信息将很难被解读,方法调用的堆栈都是一些混淆之后的名字,通过retrace.jar可以将异常的堆栈信息中的方法名还原成混淆前的名字,方便程序解决bug。
  1. 基本命令
  • keep类关键字
    • keep:保留类和类中的成员,防止他们被混淆
    • keepnames:保留类和类中的成员防止被混淆,但成员如果没有被引用将被删除
    • keepclassmember:只保留类中的成员,防止被混淆和移除
    • keepclassmembernames:值保留类中的成员,但是如果成员没有被引用将被删除
    • keepclasseswithmember:如果当前类中包含指定的方法,则保留类和类成员,否则将被混淆。
    • keepclasseswithmembernames:如果当前类中包含指定的方法,则保留类和类成员,如果类成员没有被引用则会被移除。
    • keepattributes Signature:避免混淆泛型
    • keepattributes SourceFile,LineNumberTable:抛出异常时保留行号。
  • dontwarn:忽视警告
  • optimizationpasses 5:代码混淆的压缩比,0~7之间,默认是5,一般不做修改
  1. 编写ProGuard文件
    1. 基本混淆
   # 代码混淆压缩比,在0和7之间,默认为5,一般不需要改
-optimizationpasses 5
 
# 混淆时不使用大小写混合,混淆后的类名为小写
-dontusemixedcaseclassnames
 
# 指定不去忽略非公共的库的类
-dontskipnonpubliclibraryclasses
 
# 指定不去忽略非公共的库的类的成员
-dontskipnonpubliclibraryclassmembers
 
# 不做预校验,preverify是proguard的4个步骤之一
# Android不需要preverify,去掉这一步可加快混淆速度
-dontpreverify
 
# 有了verbose这句话,混淆后就会生成映射文件
# 包含有类名->混淆后类名的映射关系
# 然后使用printmapping指定映射文件的名称
-verbose
-printmapping proguardMapping.txt
 
# 指定混淆时采用的算法,后面的参数是一个过滤器
# 这个过滤器是谷歌推荐的算法,一般不改变
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
 
# 保护代码中的Annotation不被混淆,这在JSON实体映射时非常重要,比如fastJson
-keepattributes *Annotation*
 
# 避免混淆泛型,这在JSON实体映射时非常重要,比如fastJson
-keepattributes Signature
 
//抛出异常时保留代码行号,在异常分析中可以方便定位
-keepattributes SourceFile,LineNumberTable

-dontskipnonpubliclibraryclasses用于告诉ProGuard,不要跳过对非公开类的处理。默认情况下是跳过的,因为程序中不会引用它们,有些情况下人们编写的代码与类库中的类在同一个包下,并且对包中内容加以引用,此时需要加入此条声明。

-dontusemixedcaseclassnames,这个是给Microsoft Windows用户的,因为ProGuard假定使用的操作系统是能区分两个只是大小写不同的文件名,但是Microsoft Windows不是这样的操作系统,所以必须为ProGuard指定-dontusemixedcaseclassnames选项
  1. 不需要混淆设置
# 枚举类不能被混淆
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

# 保留自定义控件(继承自View)不能被混淆
-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* ();
}

# 保留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();
}

# 对R文件下的所有类及其方法,都不能被混淆
-keepclassmembers class **.R$* {
    *;
}

# 对于带有回调函数onXXEvent的,不能混淆
-keepclassmembers class * {
    void *(**On*Event);
}
  1. APP量身定制
  • 保留实体类和成员被混淆
# 保留实体类和成员不被混淆
-keep public class com.xxxx.entity.** {
    public void set*(***);
    public *** get*();
    public *** is*();
}
  • 保留内部类
# 保留内嵌类不被混淆
-keep class com.example.xxx.MainActivity$* { *; }
  • 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);
}
  • JavaScript的处理
# 保留JS方法不被混淆
-keepclassmembers class com.example.xxx.MainActivity$JSInterface1 {
    <methods>;
}
  • 处理的反射
    在程序中使用 SomeClass.class.method 这样静态方法,在ProGuard中是在压缩过程中被保留的,那么对于Class.forName("SomeClass")呢,SomeClass不会被压缩过程中移除,它会检查程序中使用的Class.forName 方法,对参数SomeClass法外开恩,不会被移除。但是在混淆过程中,无论是Class.forName("SomeClass"),还是SomeClass.class,都不能蒙混过关,SomeClass这个类名称会被混淆,因此,我们要在ProGuard.cfg文件中保留这个名称。
Class.forName("SomeClass")
SomeClass.class
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")
  • 自定义View的处理
    但凡在Layout目录下XML布局文件配置的自定义View,都不能进行混淆。为此要遍历Layout下所有XML布局文件,找到那些自定义的View,然后确认其是否在ProGuard文件中保留。有一种思路是,在我们使用自定义View时,前面都必须加上我们的包名,比如com.a.b.customview,我们可以遍历所有Layout下的XML布局文件,查找所有匹配的com.a.b标签即可
  • 第三方jar包的处理
    一般而言,这些SDK是经过ProGuard混淆的,而我们所需要做的就是避免这些SDK的类和方法在我们APP被混淆。
# 针对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.** { *; }
-keep public class * extends android.support.v4.**
-keep public class * extends android.app.Fragment
# 对alipay的混淆处理
-libraryjars libs/alipaysdk.jar
-dontwarn com.alipay.android.app.**
-keep public class com.alipay.**  { *; }

注意事项

  1. 如何确保混淆不会对项目产生影响
    测试工作要基于混淆进行,才能尽早发现问题,开发团队的冒烟测试,也是要基于混淆包,发版前,重点的功能和模块要额外的测试,包括推送,分享等
  2. 打包时忽略警告
    当打包的时候,会发现很多could not reference class之类的warning信息,如果确认App在运行中和那些以后能用没有什么关系,可以添加-dontwarn 标签,就不会提示这些警告信息了。
  3. 对于自定义类库的混淆处理
    比如我们引用了一个叫做AndroidLib的类库,我们需要对Lib也进行混淆,然后在主项目的混淆文件中保留AndroidLib中的类和类成员
  4. 使用annotation避免混淆
    另一种类或者属性被混淆的方式时,使用annotation,如下:
@keep
@keepPublicGetterSetters
public class Bean{
   public  boolean booleanProperty;
   public  int intProperty;
   public  String stringProperty;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容