如需转载请评论或简信,并注明出处,未经允许不得转载
目录
前言
一个发布后的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
需要从代码层面检查。混淆打包后在
/build/outputs/mapping/release/
目录下会输出mapping.txt
文件,该文件提供混淆前后类、方法、类成员等的对照表,可以检查重点关注的类的混淆结果需要从测试方面检查。将混淆过的包进行全方面测试,检查是否有 bug 产生