Android 代码混淆

我们直接用 Android Studio 来说明如何进行混淆,Android Studio 自身集成 Java 语言的 ProGuard 作为压缩,优化和混淆工具,配合 Gradle 构建工具使用很简单,只需要在工程应用目录的 gradle 文件中设置 minifyEnabled 为 true 即可。然后我们就可以到 proguard-rules.pro 文件中加入我们的混淆规则了。


[TOC]

1、混淆目的

通过代码混淆可以将项目中的类、方法、变量等信息进行重命名,变成一些无意义的简短名字,同时也可以移除未被使用的类、方法、变量等。所以直观的看,通过混淆可以提高程序的安全性,增加逆向工程的难度,同时也有效缩减了apk的体积

2、开启混淆

在基于Android Studio项目的app module的build.gradle中有如下默认代码片段:

  1. buildTypes {
  2. release {
  3. // 是否支持zip
  4. zipAlignEnabled true
  5. // 注:Android Studio 2.3版本之后默认开启移除无用资源文件
  6. // 是否进行混淆
  7. minifyEnabled true
  8. // 开启资源文件压缩
  9. shrinkResources true
  10. // 是否支持调试 测试版开启 正式版关闭
  11. debuggable false
  12. proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
  13. }
  14. }

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、添加混淆配置文件

  1. # -----------------------------基本配置 -----------------------------
  2. # 指定代码的压缩级别 0 - 7(指定代码进行迭代优化的次数,在Android里面默认是5,这条指令也只有在可以优化时起作用。)
  3. -optimizationpasses 5
  4. # 混淆时不会产生形形色色的类名(混淆时不使用大小写混合类名)
  5. -dontusemixedcaseclassnames
  6. # 指定不去忽略非公共的库类(不跳过library中的非public的类)
  7. -dontskipnonpubliclibraryclasses
  8. # 指定不去忽略包可见的库类的成员
  9. -dontskipnonpubliclibraryclassmembers
  10. #不进行优化,建议使用此选项,(不优化输入的类文件)
  11. -dontoptimize
  12. # 不做预校验,可加快混淆速度
  13. # preverifyproguard4个步骤之一
  14. # Android不需要preverify,去掉这一步可以加快混淆速度
  15. -dontpreverify
  16. # 屏蔽警告
  17. -ignorewarnings
  18. # 指定混淆时采用的算法,后面的参数是一个过滤器
  19. # 这个过滤器是谷歌推荐的算法,一般不做更改
  20. -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* 、
  21. # 保护代码中的Annotation不被混淆
  22. -keepattributes *Annotation*
  23. # 避免混淆泛型, 这在JSON实体映射时非常重要
  24. -keepattributes Signature
  25. # 抛出异常时保留代码行号
  26. -keepattributes SourceFile,LineNumberTable
  27. #优化时允许访问并修改有修饰符的类和类的成员,这可以提高优化步骤的结果。
  28. # 比如,当内联一个公共的getter方法时,这也可能需要外地公共访问。
  29. # 虽然java二进制规范不需要这个,要不然有的虚拟机处理这些代码会有问题。当有优化和使用-repackageclasses时才适用。
  30. #指示语:不能用这个指令处理库中的代码,因为有的类和类成员没有设计成public ,而在api中可能变成public
  31. -allowaccessmodification
  32. #当有优化和使用-repackageclasses时才适用。
  33. -repackageclasses ''
  34. # 混淆时记录日志(打印混淆的详细信息)
  35. # 这句话能够使我们的项目混淆后产生映射文件
  36. # 包含有类名->混淆后类名的映射关系
  37. -verbose
  38. # 指定映射文件的名称
  39. -printmapping proguardMapping.txt
  1. # -----------------------------Android开发中一些需要保留的公共部分-----------------------------
  2. # 保留我们使用的四大组件,自定义的Application等等这些类不被混淆# 因为这些子类都有可能被外部调用
  3. -keep public class * extends android.app.Activity
  4. -keep public class * extends android.app.Appliction
  5. -keep public class * extends android.app.Service
  6. -keep public class * extends android.content.BroadcastReceiver
  7. -keep public class * extends android.content.ContentProvider
  8. -keep public class * extends android.app.backup.BackupAgentHelper
  9. -keep public class * extends android.preference.Preference
  10. -keep public class * extends android.view.View
  11. -keep public class com.android.vending.licensing.ILicensingService
  12. # 保留support下的所有类及其内部类
  13. -keep class android.support.** {*;}
  14. -dontwarn javax.annotation.**
  15. ## 保留继承的
  16. ## support-v4
  17. #-dontwarn android.support.v4.**
  18. #-keep class android.support.v4.** { *; }
  19. #-keep interface android.support.v4.** { *; }
  20. #-keep public class * extends android.support.v4.**
  21. ## support-v7
  22. #-dontwarn android.support.v7.** #去掉警告
  23. #-keep class android.support.v7.** { *; } #过滤android.support.v7
  24. #-keep interface android.support.v7.app.** { *; }
  25. #-keep public class * extends android.support.v7.**
  26. #-keep public class * extends android.support.annotation.**
  27. # 表示不混淆任何包含native方法的类的类名以及native方法名,这个和我们刚才验证的结果是一致
  28. -keepclasseswithmembernames class * {
  29. native <methods>;
  30. }
  31. # 这个主要是在layout 中写的onclick方法android:onclick="onClick",不进行混淆
  32. # 表示不混淆Activity中参数是View的方法,因为有这样一种用法,在XML中配置android:onClick=”buttonClick”属性,
  33. # 当用户点击该按钮时就会调用Activity中的buttonClick(View view)方法,如果这个方法被混淆的话就找不到了
  34. -keepclassmembers class * extends android.support.v7.app.AppCompetActivity{
  35. public void *(android.view.View);
  36. }
  37. # 表示不混淆枚举中的values()和valueOf()方法
  38. -keepclassmembers enum * {
  39. public static **[] values();
  40. public static ** valueOf(java.lang.String);
  41. }
  42. # 保持自定义控件类不被混淆
  43. # 表示不混淆任何一个View中的setXxx()和getXxx()方法,
  44. # 因为属性动画需要有相应的settergetter的方法实现,混淆了就无法工作了。
  45. -keep public class * extends android.view.View{
  46. *** get*();
  47. void set*(***);
  48. public <init>(android.content.Context);
  49. public <init>(android.content.Context, android.util.AttributeSet);
  50. public <init>(android.content.Context, android.util.AttributeSet, int);
  51. }
  52. # 保持自定义控件类不被混淆,指定格式的构造方法不去混淆
  53. -keepclasseswithmembers class * {
  54. public <init>(android.content.Context);
  55. public <init>(android.content.Context, android.util.AttributeSet);
  56. public <init>(android.content.Context, android.util.AttributeSet, int);
  57. }
  58. # 保护指定的类和类的成员,但条件是所有指定的类和类成员是要存在
  59. -keepclasseswithmembernames class * {
  60. public <init>(android.content.Context, android.util.AttributeSet);
  61. public <init>(android.content.Context, android.util.AttributeSet, int);
  62. }
  63. # 表示不混淆Parcelable实现类中的CREATOR字段,
  64. # 毫无疑问,CREATOR字段是绝对不能改变的,包括大小写都不能变,不然整个Parcelable工作机制都会失败。
  65. -keep class * implements android.os.Parcelable {
  66. public static final android.os.Parcelable$Creator *;
  67. }
  68. # 这指定了继承Serizalizable的类的如下成员不被移除混淆
  69. -keepclassmembers class * implements java.io.Serializable {
  70. static final long serialVersionUID;
  71. private static final java.io.ObjectStreamField[] serialPersistentFields;
  72. private void writeObject(java.io.ObjectOutputStream);
  73. private void readObject(java.io.ObjectInputStream);
  74. java.lang.Object writeReplace();
  75. java.lang.Object readResolve();
  76. }
  77. # 保留R下面的资源
  78. -keep class **.R$* {
  79. *;
  80. }
  81. -keep public class **.R$*{
  82. public static final int *;
  83. }
  84. #不混淆资源类下static
  85. -keepclassmembers class **.R$* {
  86. public static <fields>;
  87. }
  88. # 对于带有回调函数的onXXEvent、**On*Listener的,不能被混淆
  89. -keepclassmembers class * {
  90. void *(**On*Event);
  91. void *(**On*Listener);
  92. }
  93. # 保留我们自定义控件(继承自View)不被混淆
  94. -keep public class * extends android.view.View{
  95. *** get*();
  96. void set*(***);
  97. public <init>(android.content.Context);
  98. public <init>(android.content.Context, android.util.AttributeSet);
  99. public <init>(android.content.Context, android.util.AttributeSet, int);
  100. }
  101. # 保留在Activity中的方法参数是View的方法
  102. # 从而我们在layout里边编写onClick就不会被影响
  103. -keepclassmembers class * extends android.app.Activity {
  104. public void *(android.view.View);
  105. }
  1. #----------------------------- WebView(项目中没有可以忽略) --------------------------
  2. -keepclassmembers class fqcn.of.javascript.interface.for.Webview {
  3. public *;
  4. }
  5. -keepclassmembers class * extends android.webkit.WebViewClient {
  6. public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
  7. public boolean *(android.webkit.WebView, java.lang.String);
  8. }
  9. -keepclassmembers class * extends android.webkit.WebViewClient {
  10. public void *(android.webkit.WebView, jav.lang.String);
  11. }
  12. # app中与HTML5JavaScript的交互进行特殊处理
  13. # 我们需要确保这些js要调用的原生方法不能够被混淆,于是我们需要做如下处理:
  14. -keepclassmembers class com.ljd.example.JSInterface {
  15. <methods>;
  16. }
  1. # ----------------------------- 其他的 -----------------------------
  2. # 删除代码中Log相关的代码
  3. -assumenosideeffects class android.util.Log {
  4. public static boolean isLoggable(java.lang.String, int);
  5. public static int v(...);
  6. public static int i(...);
  7. public static int w(...);
  8. public static int d(...);
  9. public static int e(...);
  10. }
  11. # 保持测试相关的代码
  12. -dontnote junit.framework.**
  13. -dontnote junit.runner.**
  14. -dontwarn android.test.**
  15. -dontwarn android.support.test.**
  16. -dontwarn org.junit.**
  1. #---------------------------------根据个人项目进行私有配置(以实体类和注解为例)---------------------------------
  2. #--------(实体Model不能混淆,否则找不到对应的属性获取不到值)-----
  3. -dontwarn com.mtf.test.model.**
  4. #对含有反射类的处理
  5. -keep class com.mtf.test.model.** { *; }
  6. #---------自定义的注解--------
  7. -keep class com.mtf.test.permission.** {*;}
  8. # 不混淆使用了注解的类及类成员
  9. -keep @com.mtf.test.permission class * {*;}
  10. # 如果类中有使用了注解的方法,则不混淆类和类成员
  11. -keepclasseswithmembers class * { @com.mtf.test.permission <methods>;}
  12. # 如果类中有使用了注解的字段,则不混淆类和类成员
  13. -keepclasseswithmembers class * { @com.mtf.test.permission <fields>;}
  14. # 如果类中有使用了注解的构造函数,则不混淆类和类成员
  15. -keepclasseswithmembers class * { @com.mtf.test.permission <init>(...);}
  1. #-------------------最后不要忘记添加项目中第三方引用的混淆规则---------------
  2. #----------以友盟为例---------
  3. -dontwarn com.umeng.**
  4. -dontwarn com.tencent.weibo.sdk.**
  5. -keep public interface com.tencent.**
  6. -keep public interface com.umeng.socialize.**
  7. -keep public interface com.umeng.socialize.sensor.**
  8. -keep public interface com.umeng.scrshot.**
  9. -keep public class com.umeng.socialize.* {*;}
  10. -keep class com.umeng.scrshot.**
  11. -keep public class com.tencent.** {*;}
  12. -keep class com.umeng.socialize.sensor.**
  13. -keep class com.umeng.socialize.handler.**
  14. -keep class com.umeng.socialize.handler.*
  15. -keep class com.tencent.mm.sdk.modelmsg.WXMediaMessage {*;}
  16. -keep class com.tencent.mm.sdk.modelmsg.** implements com.tencent.mm.sdk.modelmsg.WXMediaMessage$IMediaObject {*;}
  17. -keep class im.yixin.sdk.api.YXMessage {*;}
  18. -keep class im.yixin.sdk.api.** implements im.yixin.sdk.api.YXMessage$YXMessageData{*;}
  19. -keep class com.tencent.** {*;}
  20. -dontwarn com.tencent.**
  21. -keep public class com.umeng.soexample.R$*{
  22. public static final int *;
  23. }
  24. -keep class com.tencent.open.TDialog$*
  25. -keep class com.tencent.open.TDialog$* {*;}
  26. -keep class com.tencent.open.PKDialog
  27. -keep class com.tencent.open.PKDialog {*;}
  28. -keep class com.tencent.open.PKDialog$*
  29. -keep class com.tencent.open.PKDialog$* {*;}
  30. -keep class com.sina.** {*;}
  31. -dontwarn com.sina.**
  32. -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。

(完)