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 {// 是否支持zipzipAlignEnabled true// 注:Android Studio 2.3版本之后默认开启移除无用资源文件// 是否进行混淆minifyEnabled true// 开启资源文件压缩shrinkResources true// 是否支持调试 测试版开启 正式版关闭debuggable falseproguardFiles 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。
(完)