关于你想知道的 Android 混淆都在这儿了!

混淆优点:

  • 防止第三方反编译,窃取自己的劳动成果;
  • 减小 apk 体积,节省资源;

混淆带来的问题:
做过 Android 开发的童鞋应该都有此体会,明明我的 debug 包在调试的时候一切正常啊,发布到线上了怎么就 crash 了呢?一般涉及到 debug 和 release 包的时候问题,很多都是由于混淆引起的,因此在这里对混淆做一个小小的总结,以便于后续备用。

1. 混淆原则:

  • 反射用到的类不混淆;
  • JNI 方法不混淆;
  • AndroidMainfest 中的类不混淆,四大组件和 Application 的子类和 Framework 层下所有的类默认不会进行混淆;
  • Parcelable 的子类和 Creator 静态成员变量不混淆,否则会产生android.os.BadParcelableException 异常;
  • 使用 GSON、fastjson 等框架时,所写的 JSON 对象类不混淆,否则无法将 JSON 解析成对应的对象;
  • 使用第三方开源库或者引用其他第三方的 SDK 包时,需要在混淆文件中加入对应的混淆规则;

2. 混淆语法:

  • 通用匹配规则:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ? 匹配单个字符
    * 匹配类名中的任何部分,但不包含额外的包名
    ** 匹配类名中的任何部分,并且可以包含额外的包名
    % 匹配任何基础类型的类型名
    ... 匹配任意数量、任意类型的参数
    <init> 匹配任何构造器
    <ifield> 匹配任何字段名
    <methods> 匹配所有方法方法
    * 匹配任意类型名 ,包含基础类型/非基础类型
    *(当用在类内部时) 匹配任何字段和方法
    $ 指内部类
  • 不混淆某个包下所有的类:

    1
    -keep class packageName.**{*;}
  • 不混淆指定的类:

    1
    -keep class packageName.className{*;}
  • 不混淆某个类的构造函数:

    1
    2
    3
    -keep clsss packageName.className {
    public <init>(...);
    }
  • 不混淆某个接口:

    1
    -keep interface packageName.interfaceName{*;}
  • 不混淆接口的实现:

    1
    -keep class * implements packageName.interfaceName{*;}

3. Android app/library 混淆配置:

  • app 配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    android {
    buildTypes {
    release {
    minifyEnabled true
    shrinkResources true
    proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
    }
    }
  • aar/jar: 开源库中可以依赖此标志来指定库的混淆方式,此会将*.pro文件打包进入aar中,库混淆时候会自动使用此混淆配置文件,以 consumerProguardFiles 方式加入的混淆具有以下特性:

    • *.pro 文件会包含在 aar 文件中;
    • pro 文件中的配置会在混淆的时候被使用;
    • consumerProguardFiles 配置只针对此 aar 进行混淆配置;
    • consumerProguardFiles 配置只对库文件有效,对应用程序无效;
1
2
3
4
5
6
android {
defaultConfig {
minifyEnabled true
consumerProguardFiles 'consumer-proguard-rules.pro'
}
}

4. proguard 混淆后输出:

  • dump.txt 描述 apk 文件中所有类文件间的内部结构;
  • mapping.txt 列出了原始的类,方法,和字段名与混淆后代码之间的映射;
  • seeds.txt 列出了未被混淆的类和成员;
  • usage.txt 列出了从 apk 中删除的代码

5. 自定义输出 mapping.txt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
android {
applicationVariants.all { variant ->
variant.outputs.each { output ->
if (variant.getBuildType().isMinifyEnabled()) {
variant.assemble.doLast {
copy {
from variant.mappingFile
into "${projectDir}/mappings"
rename { String fileName ->
"mapping-${variant.name}.txt"
}
}
}
}
}
}
}

6. 下面列举一些常用的混淆配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#============================================基本配置============================================
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
#优化:设置混淆的压缩比率 0 ~ 7
-optimizationpasses 5
#优化时允许访问并修改有修饰符的类和类的成员
-allowaccessmodification
#不预校验
-dontpreverify
#混淆时不会产生大小写混合的类名
-dontusemixedcaseclassnames
#指定不去忽略非公共的库类。在4.5版本中是默认配置。
-dontskipnonpubliclibraryclasses
#输出更多信息,如果处理过程中报错,会输出跟踪错误的所有信息
-verbose
#保留注解|避免使用泛型的位置混淆后出现类型转换错误|内部类
-keepattributes SourceFile,LineNumberTable
#打印出所有的关于错误引用或任何重要的警告,但是proguard继续处理
-ignorewarnings
#保留源码的行号、源文件信息
-renamesourcefileattribute Source
-keepattributes *Annotation*,InnerClasses
-keep class * extends java.lang.annotation.Annotation { *; }
#============================================基本配置============================================
#============================================默认配置============================================
#保留View子类读取XML的构造方法:
-keep public class * extends android.view.View {
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
public void set*(...);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclassmembers class **.R$* {
public static <fields>;
}
# 在layout 中写的onclick方法android:onclick="onClick",不进行混淆
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
# native 方法
-keepclasseswithmembernames class * {
native <methods>;
}
# 枚举类
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# 序列化
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}
#泛型,解决出现类型转换错误的问题
-keepattributes Signature
# Serializable序列化
-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();
}
# 忽略 android.support 包下代码的警告
-dontwarn android.support.**
# 不混淆 Keep 类
-keep class android.support.annotation.Keep
-keep @android.support.annotation.Keep class * {*;}
# 不混淆所有类中用 @Keep 注解的方法
-keepclasseswithmembers class * {
@android.support.annotation.Keep <methods>;
}
# 不混淆所有类中用 @Keep 的变量
-keepclasseswithmembers class * {
@android.support.annotation.Keep <fields>;
}
# 不混淆所有类中用 @Keep 注解的构造方法
-keepclasseswithmembers class * {
@android.support.annotation.Keep <init>(...);
}
-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);
}
#============================================默认配置============================================
坚持原创技术分享,您的支持将鼓励我继续创作!