Android代码混淆使用手册

如需转载请评论或简信,并注明出处,未经允许不得转载

Android代码混淆使用手册_第1张图片

目录

Android代码混淆使用手册_第2张图片

前言

一个发布后的apk可以通过反编译代码反编译资源重新打包后变成一个新的apk。使用代码混淆技术可以让反编译后的代码难以阅读(注意混淆不是让apk不能阅读,而是加大阅读的难度),还可以通过压缩优化代码,从而减小APK的体积,甚至在一定程度上还能减小应用运行时的内存

混淆原理

混淆包括四个功能,shrinker(压缩), optimizer(优化),obfuscator(混淆),preverifier(预校验)

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

哪些不应该混淆

  • 使用了自定义控件那么要保证它们不参与混淆
  • 使用了枚举要保证枚举不被混淆
  • 对第三方库中的类不进行混淆
  • 运用了反射的类也不进行混淆
  • 使用了 Gson 之类的工具要使 JavaBean 类即实体类不被混淆
  • 在引用第三方库的时候,一般会标明库的混淆规则的,建议在使用的时候就把混淆规则添加上去,免得到最后才去找
  • 有用到 WebView 的 JS 调用也需要保证写的接口方法不混淆,原因和第一条一样
  • Parcelable 的子类和 Creator 静态成员变量不混淆,否则会产生 Android.os.BadParcelableException 异常
  • 使用的四大组件,自定义的Application 实体类
  • JNI中调用的类
  • Layout布局使用的View构造函数(自定义控件)、android:onClick
  • SDK(Jar、aar)提供给外部调用的方法

混淆步骤

打开混淆

在build.gradle文件下,将minifyEnabled的值设为true代表开启混淆,proguardFiles代表混淆文件的地址

buildTypes {
     release {
     minifyEnabled true
     proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
   }
}

因为开启混淆会使编译时间变长,且不利于断点调试,所以debug模式下不开启

扩展:release下还有一些配置是可以加上的

zipAlignEnabled true  // Zipalign优化
shrinkResources false // 删除无用资源
debuggable false // 是否debug
signingConfig signingConfigs.relealse  // 签名

编写混淆文件

这里提供一个模板,基本指令区默认保留区WebView都是可以基本所有项目共用的,我们要做的事根据自己的实际项目区完善实体类第三方包与js互相调用的类反射相关的类和方法

#---------------------------------1.实体类---------------------------------



#-------------------------------------------------------------------------

#---------------------------------2.第三方包-------------------------------



#-------------------------------------------------------------------------

#---------------------------------3.与js互相调用的类------------------------



#-------------------------------------------------------------------------

#---------------------------------4.反射相关的类和方法-----------------------



#----------------------------------------------------------------------------

#---------------------------------基本指令区----------------------------------
-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers
-dontpreverify
-verbose
-printmapping proguardMapping.txt
-optimizations !code/simplification/cast,!field/*,!class/merging/*
-keepattributes *Annotation*,InnerClasses
-keepattributes Signature
-keepattributes SourceFile,LineNumberTable
#----------------------------------------------------------------------------

#---------------------------------默认保留区---------------------------------
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Appliction
-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 public class com.android.vending.licensing.ILicensingService
-keep class android.support.** {*;}

-keepclasseswithmembernames class * {
    native ;
}
-keepclassmembers class * extends android.app.Activity{
    public void *(android.view.View);
}
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}
-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);
}
-keep class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private 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();
}
-keep class **.R$* {
 *;
}
-keepclassmembers class * {
    void *(**On*Event);
}
#----------------------------------------------------------------------------

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

1.实体类

你可以针对单个实体类不进行混淆

-keep class 你的实体类所在的包.** { *; }

如果你的实体类都放在一个包下,如路径是com/geekholt/demo/bean,也可以用下面的方法对整个包下的类都不混淆

-keep class com.geekholt.demo.bean.** { *; }

2.第三方包

可以在build.gradle下查看项目引用到了哪些第三方包,一般可以到对应的官网查看混淆配置,这里列出了一些常用的第三方包的混淆配置

  • Gson
-dontwarn com.google.**
-keep class com.google.gson.** {*;}
  • Eventbus3
-keepattributes *Annotation*
-keepclassmembers class ** {
    @org.greenrobot.eventbus.Subscribe ;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }

# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
    (java.lang.Throwable);
}
  • Glide4
-keep public class * implements com.bumptech.glide.module.AppGlideModule
-keep public class * implements com.bumptech.glide.module.LibraryGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
  **[] $VALUES;
  public *;
}
  • 友盟统计
-keepclassmembers class * {
    public  (org.json.JSONObject);
}
#友盟统计5.0.0以上SDK需要
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}
#友盟统计R.java删除问题
-keep public class com.gdhbgh.activity.R$*{
    public static final int *;
}
  • OkHttp3
-dontwarn com.squareup.okhttp3.**
-keep class com.squareup.okhttp3.** { *;}
-dontwarn okio.**
  • Retrofit2
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes Exceptions
  • RxJava、RxAndroid
-dontwarn sun.misc.**
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
   long producerIndex;
   long consumerIndex;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
    rx.internal.util.atomic.LinkedQueueNode producerNode;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
    rx.internal.util.atomic.LinkedQueueNode consumerNode;
}
  • 支付宝
-keep class com.alipay.android.app.IAlixPay{*;}
-keep class com.alipay.android.app.IRemoteServiceCallback{*;}
-keep class com.alipay.android.app.IRemoteServiceCallback$Stub{*;}
-keep class com.alipay.sdk.app.PayTask{
    public *;
}
-keep class com.alipay.sdk.app.AuthTask{
    public *;
}
  • JPUSH
-dontwarn cn.jpush.**
-keep class cn.jpush.** {*;}

# protobuf(jpush依赖)
-dontwarn com.google.**
-keep class com.google.protobuf.** {*;}
  • GreenDAO 3
-keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {
public static java.lang.String TABLENAME;
}
-keep class **$Properties
 
# If you do not use SQLCipher:
-dontwarn org.greenrobot.greendao.database.**
# If you do not use Rx:
-dontwarn rx.**

3.与js互相调用的类

第三部分与js互调的类,工程中没有直接跳过

-keep class 你的类所在的包.** { *; }

如果是内部类的话,你可以这样

-keepclasseswithmembers class 你的类所在的包.父类$子类 { ; }
-keepclasseswithmembers class com.demo.login.bean.ui.MainActivity$JSInterface { 
      ; 
}

4.与反射有关的类

工程中没有直接跳过

-keep class 你的类所在的包.** { *; }

Keep配置

保留 防止被移除或者被重命名 防止被重命名
类和类成员 -keep -keepnames
仅类成员 -keepmembers -keepmembernames
如果拥有某成员,保留类和类成员 -keepclasseswithmembers -keepclasseswithmembernames

如果不确定自己该用哪个的话,就用-keep,它能保证匹配的类在压缩这一阶段不被移除,并且在混淆阶段不会被重新命名。

  • 如果只声明保护一个类,并没有指定受保护的成员。proguard只会保护它的类名和它的无参构造函数。其它成员依旧会被压缩、优化、混淆。
  • 如果声明保护一个方法,proguard会把它当作程序的入口点,方法名不会变,但它里面的代码依旧会被优化、混淆。

Proguard通配符

Proguard通配符 描述
匹配类中的所有字段
匹配类中所有的方法
匹配类中所有的构造函数
* 匹配任意长度字符,不包含包名分隔符(.)
** 匹配任意长度字符,包含包名分隔符(.)
*** 匹配任意参数类型
... ...

常用自定义混淆规则

  • 不混淆某个类
-keep public class com.geekholt.example.Test { *; }
  • 不混淆某个包所有的类
-keep class com.geekholt.test.** { *; }
}
  • 不混淆某个类的子类
-keep public class * extends com.geekholt.example.Test { *; }
  • 不混淆所有类名中包含了“model”的类及其成员
-keep public class **.*model*.** { *; }
  • 不混淆某个接口的实现
-keep class * implements com.geekholt.example.TestInterface { *; }
  • 不混淆某个类的构造方法
-keepclassmembers class com.geekholt.example.Test { 
    public (); 
}
  • 不混淆某个类的特定的方法
-keepclassmembers class com.geekholt.example.Test { 
    public void test(java.lang.String); 
}
}
  • 不混淆某个类的内部类
-keep class com.geekholt.example.Test$* {
        *;
 }

注意事项

混淆过的包必须进行检查,避免因混淆引入的bug

  1. 需要从代码层面检查。混淆打包后在/build/outputs/mapping/release/目录下会输出mapping.txt文件,该文件提供混淆前后类、方法、类成员等的对照表,可以检查重点关注的类的混淆结果

  2. 需要从测试方面检查。将混淆过的包进行全方面测试,检查是否有 bug 产生

你可能感兴趣的:(Android代码混淆使用手册)