Android代码混淆使用手册

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

目录

前言

一个发布后的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 <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }

# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
    <init>(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 <init> (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通配符 描述
<field> 匹配类中的所有字段
<method> 匹配类中所有的方法
<init> 匹配类中所有的构造函数
* 匹配任意长度字符,不包含包名分隔符(.)
** 匹配任意长度字符,包含包名分隔符(.)
*** 匹配任意参数类型
... ...

常用自定义混淆规则

  • 不混淆某个类
-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 <init>(); 
}
  • 不混淆某个类的特定的方法
-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 产生

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266