Android 代码混淆
我们直接用 Android Studio 来说明如何进行混淆,Android Studio 自身集成 Java 语言的 ProGuard 作为压缩,优化和混淆工具,配合 Gradle 构建工具使用很简单,只需要在工程应用目录的 gradle 文件中设置 minifyEnabled 为 true 即可。然后我们就可以到 proguard-rules.pro 文件中加入我们的混淆规则了。
[TOC]
1、混淆目的
通过代码混淆可以将项目中的类、方法、变量等信息进行重命名,变成一些无意义的简短名字,同时也可以移除未被使用的类、方法、变量等。所以直观的看,通过混淆可以提高程序的安全性,增加逆向工程的难度,同时也有效缩减了apk的体积
2、开启混淆
在基于Android Studio项目的app module的build.gradle中有如下默认代码片段:
buildTypes {
release {
// 是否支持zip
zipAlignEnabled true
// 注:Android Studio 2.3版本之后默认开启移除无用资源文件
// 是否进行混淆
minifyEnabled true
// 开启资源文件压缩
shrinkResources true
// 是否支持调试 测试版开启 正式版关闭
debuggable false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
proguard-android.txt
代表系统默认的混淆规则配置文件,该文件在Android SDK目录>/tools/proguard
下,一般不要更改该配置文件,因为也会作用于其它项目,除非你能确保所做的更改不影响其它项目的混淆。proguard-rules.pro
代码表当前project
的混淆配置文件,在app module
下,可以通过修改该文件来添加适用当前项目的混淆规则。
3、混淆的关键字和通配符
- Proguard 关键字
关键字 | 含义 |
---|---|
dontwarn | dontwarn 是一个和 keep 可以说是形影不离,尤其是处理引入的 library 时. |
keep | 保留类和类成员,防止被混淆或移除 |
keepnames | 保留类和类成员,防止被混淆,但没有被引用的类成员会被移除 |
keepclassmembers | 只保留类成员,防止被混淆或移除 |
keepclassmembernames | 只保留类成员,防止被混淆,但没有被引用的成员会被移除 |
keepclasseswithmembers | 保留类和类成员,防止被混淆或移除,如果指定的类成员不存在还是会被混淆 |
keepclasseswithmembernames | 保留类和类成员,防止被混淆,如果指定的类成员不存在还是会被混淆,没有被引用的类成员会被移除 |
- 通配符
通配符 | 含义 |
---|---|
* | 匹配任意长度字符,但不含包名分隔符.。例如一个类的全包名路径是com.mtf.test.Person ,使用com.mtf.test.* 是可以匹配的,但com.mtf.* 就不能匹配 |
** | 匹配任意长度字符,并包含包名分隔符.。例如要匹配com.mtf.test.** 包下的所有内容 |
*** | 匹配任意参数类型。例如*** getName(***) 可匹配String getName(String) |
… | 匹配任意长度的任意类型参数。例如void setName(...) 可匹配void setName(String firstName, String secondName) |
<fileds> | 匹配类、接口中所有字段 |
<methods> | 匹配类、接口中所有方法 |
<init> | 匹配类中所有构造函数 |
4、注意不应该参与混淆的文件或类
- 使用了自定义控件那么要保证它们不参与混淆
- 使用了枚举要保证枚举不被混淆
- 对第三方库中的类不进行混淆
- 运用了反射的类也不进行混淆
- 使用了 Gson 之类的工具要使 JavaBean 类即实体类不被混淆
- 在引用第三方库的时候,一般会标明库的混淆规则的,建议在使用的时候就把混淆规则添加上去,免得到最后才去找
- 有用到 WebView 的 JS 调用也需要保证写的接口方法不混淆,原因和第一条一样
- Parcelable 的子类和 Creator 静态成员变量不混淆,否则会产生 Android.os.BadParcelableException 异常
- 使用的四大组件,自定义的 Application* 实体类
- JNI 中调用的类
- Layout 布局使用的 View 构造函数(自定义控件)、android:onClick等。
5、添加混淆配置文件
# -----------------------------基本配置 -----------------------------
# 指定代码的压缩级别 0 - 7(指定代码进行迭代优化的次数,在Android里面默认是5,这条指令也只有在可以优化时起作用。)
-optimizationpasses 5
# 混淆时不会产生形形色色的类名(混淆时不使用大小写混合类名)
-dontusemixedcaseclassnames
# 指定不去忽略非公共的库类(不跳过library中的非public的类)
-dontskipnonpubliclibraryclasses
# 指定不去忽略包可见的库类的成员
-dontskipnonpubliclibraryclassmembers
#不进行优化,建议使用此选项,(不优化输入的类文件)
-dontoptimize
# 不做预校验,可加快混淆速度
# preverify是proguard的4个步骤之一
# Android不需要preverify,去掉这一步可以加快混淆速度
-dontpreverify
# 屏蔽警告
-ignorewarnings
# 指定混淆时采用的算法,后面的参数是一个过滤器
# 这个过滤器是谷歌推荐的算法,一般不做更改
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* 、
# 保护代码中的Annotation不被混淆
-keepattributes *Annotation*
# 避免混淆泛型, 这在JSON实体映射时非常重要
-keepattributes Signature
# 抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable
#优化时允许访问并修改有修饰符的类和类的成员,这可以提高优化步骤的结果。
# 比如,当内联一个公共的getter方法时,这也可能需要外地公共访问。
# 虽然java二进制规范不需要这个,要不然有的虚拟机处理这些代码会有问题。当有优化和使用-repackageclasses时才适用。
#指示语:不能用这个指令处理库中的代码,因为有的类和类成员没有设计成public ,而在api中可能变成public
-allowaccessmodification
#当有优化和使用-repackageclasses时才适用。
-repackageclasses ''
# 混淆时记录日志(打印混淆的详细信息)
# 这句话能够使我们的项目混淆后产生映射文件
# 包含有类名->混淆后类名的映射关系
-verbose
# 指定映射文件的名称
-printmapping proguardMapping.txt
# -----------------------------Android开发中一些需要保留的公共部分-----------------------------
# 保留我们使用的四大组件,自定义的Application等等这些类不被混淆# 因为这些子类都有可能被外部调用
-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
# 保留support下的所有类及其内部类
-keep class android.support.** {*;}
-dontwarn javax.annotation.**
## 保留继承的
## support-v4
#-dontwarn android.support.v4.**
#-keep class android.support.v4.** { *; }
#-keep interface android.support.v4.** { *; }
#-keep public class * extends android.support.v4.**
## support-v7
#-dontwarn android.support.v7.** #去掉警告
#-keep class android.support.v7.** { *; } #过滤android.support.v7
#-keep interface android.support.v7.app.** { *; }
#-keep public class * extends android.support.v7.**
#-keep public class * extends android.support.annotation.**
# 表示不混淆任何包含native方法的类的类名以及native方法名,这个和我们刚才验证的结果是一致
-keepclasseswithmembernames class * {
native <methods>;
}
# 这个主要是在layout 中写的onclick方法android:onclick="onClick",不进行混淆
# 表示不混淆Activity中参数是View的方法,因为有这样一种用法,在XML中配置android:onClick=”buttonClick”属性,
# 当用户点击该按钮时就会调用Activity中的buttonClick(View view)方法,如果这个方法被混淆的话就找不到了
-keepclassmembers class * extends android.support.v7.app.AppCompetActivity{
public void *(android.view.View);
}
# 表示不混淆枚举中的values()和valueOf()方法
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# 保持自定义控件类不被混淆
# 表示不混淆任何一个View中的setXxx()和getXxx()方法,
# 因为属性动画需要有相应的setter和getter的方法实现,混淆了就无法工作了。
-keep public class * extends android.view.View{
*** get*();
void set*(***);
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
# 保持自定义控件类不被混淆,指定格式的构造方法不去混淆
-keepclasseswithmembers class * {
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
# 保护指定的类和类的成员,但条件是所有指定的类和类成员是要存在
-keepclasseswithmembernames class * {
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
# 表示不混淆Parcelable实现类中的CREATOR字段,
# 毫无疑问,CREATOR字段是绝对不能改变的,包括大小写都不能变,不然整个Parcelable工作机制都会失败。
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
# 这指定了继承Serizalizable的类的如下成员不被移除混淆
-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();
}
# 保留R下面的资源
-keep class **.R$* {
*;
}
-keep public class **.R$*{
public static final int *;
}
#不混淆资源类下static的
-keepclassmembers class **.R$* {
public static <fields>;
}
# 对于带有回调函数的onXXEvent、**On*Listener的,不能被混淆
-keepclassmembers class * {
void *(**On*Event);
void *(**On*Listener);
}
# 保留我们自定义控件(继承自View)不被混淆
-keep public class * extends android.view.View{
*** get*();
void set*(***);
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
# 保留在Activity中的方法参数是View的方法
# 从而我们在layout里边编写onClick就不会被影响
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
#----------------------------- 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);
}
# 在app中与HTML5的JavaScript的交互进行特殊处理
# 我们需要确保这些js要调用的原生方法不能够被混淆,于是我们需要做如下处理:
-keepclassmembers class com.ljd.example.JSInterface {
<methods>;
}
# ----------------------------- 其他的 -----------------------------
# 删除代码中Log相关的代码
-assumenosideeffects class android.util.Log {
public static boolean isLoggable(java.lang.String, int);
public static int v(...);
public static int i(...);
public static int w(...);
public static int d(...);
public static int e(...);
}
# 保持测试相关的代码
-dontnote junit.framework.**
-dontnote junit.runner.**
-dontwarn android.test.**
-dontwarn android.support.test.**
-dontwarn org.junit.**
#---------------------------------根据个人项目进行私有配置(以实体类和注解为例)---------------------------------
#--------(实体Model不能混淆,否则找不到对应的属性获取不到值)-----
-dontwarn com.mtf.test.model.**
#对含有反射类的处理
-keep class com.mtf.test.model.** { *; }
#---------自定义的注解--------
-keep class com.mtf.test.permission.** {*;}
# 不混淆使用了注解的类及类成员
-keep @com.mtf.test.permission class * {*;}
# 如果类中有使用了注解的方法,则不混淆类和类成员
-keepclasseswithmembers class * { @com.mtf.test.permission <methods>;}
# 如果类中有使用了注解的字段,则不混淆类和类成员
-keepclasseswithmembers class * { @com.mtf.test.permission <fields>;}
# 如果类中有使用了注解的构造函数,则不混淆类和类成员
-keepclasseswithmembers class * { @com.mtf.test.permission <init>(...);}
#-------------------最后不要忘记添加项目中第三方引用的混淆规则---------------
#----------以友盟为例---------
-dontwarn com.umeng.**
-dontwarn com.tencent.weibo.sdk.**
-keep public interface com.tencent.**
-keep public interface com.umeng.socialize.**
-keep public interface com.umeng.socialize.sensor.**
-keep public interface com.umeng.scrshot.**
-keep public class com.umeng.socialize.* {*;}
-keep class com.umeng.scrshot.**
-keep public class com.tencent.** {*;}
-keep class com.umeng.socialize.sensor.**
-keep class com.umeng.socialize.handler.**
-keep class com.umeng.socialize.handler.*
-keep class com.tencent.mm.sdk.modelmsg.WXMediaMessage {*;}
-keep class com.tencent.mm.sdk.modelmsg.** implements com.tencent.mm.sdk.modelmsg.WXMediaMessage$IMediaObject {*;}
-keep class im.yixin.sdk.api.YXMessage {*;}
-keep class im.yixin.sdk.api.** implements im.yixin.sdk.api.YXMessage$YXMessageData{*;}
-keep class com.tencent.** {*;}
-dontwarn com.tencent.**
-keep public class com.umeng.soexample.R$*{
public static final int *;
}
-keep class com.tencent.open.TDialog$*
-keep class com.tencent.open.TDialog$* {*;}
-keep class com.tencent.open.PKDialog
-keep class com.tencent.open.PKDialog {*;}
-keep class com.tencent.open.PKDialog$*
-keep class com.tencent.open.PKDialog$* {*;}
-keep class com.sina.** {*;}
-dontwarn com.sina.**
-keep class com.alipay.share.sdk.** {*;}
6、查看混淆结果
混淆后打包,会在appmodule/build/outputs/mapping/release目录下生成如下文件
dump.txt
:描述apk文件中所有类的内部结构mapping.txt
:混淆前后的类、类成员、方法的对照关系(重要,追溯Crash堆栈信息要用到)resources.txt
:资源文件的压缩信息seeds.txt
:未被混淆的类和成员usage.txt
:被移除的代码
注意混淆后的apk包,需要系统的测试,防止混淆导致的潜在bug。
(完)