Android 代码混淆与压缩遇到的坑

一、混淆基本知识

Android 开发使用 Java,为了很好的保护 Java 源代码,我们需要对编译好后的 class 文件进行混淆。

ProGuard 是一个混淆代码的开源项目,它的主要作用是混淆代码,包括以下 4 个功能。

  1. 压缩 ( Shrink ):检测并移除代码中无用的类、字段、方法和特性 ( Attribute )。

  2. 优化 ( Optimize ):对字节码进行优化,移除无用的指令。

  3. 混淆 ( Obfuscate ):使用 a, b, c,d 这样简短而无意义的名称,对类、字段和方法进行重命名。

  4. 预检 ( Preveirfy ):在 Java 平台上对处理后的代码进行预检,确保加载的 class 文件是可执行的。

二、 Android 混淆步骤

三步走:

  1. 基本混淆
  2. 针对 APP 的量身定制
  3. 针对第三方 jar 包的解决方案

1、基本混淆:

开启混淆:

1
2
3
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'

基本混淆模板

1
2
3
4
5
6
7
8
#指定压缩级别
-optimizationpasses 5
#不跳过非公共的库的类成员
-dontskipnonpubliclibraryclassmembers
#混淆时采用的算法
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
1
2
3
4
5
6
7
8
9
10
#把混淆类中的方法名也混淆了
-useuniqueclassmembernames
#优化时允许访问并修改有修饰符的类和类的成员
-allowaccessmodification
#将文件来源重命名为“SourceFile”字符串
-renamesourcefileattribute SourceFile
#保留行号
-keepattributes SourceFile,LineNumberTable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#保持所有实现 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();
}
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.preference.Preference
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.annotation.**
-keep public class * extends android.support.v7.**
1
2
3
4
5
6
7
8
# 保持测试相关的代码
-dontnote junit.framework.**
-dontnote junit.runner.**
-dontwarn android.test.**
-dontwarn android.support.test.**
-dontwarn org.junit.**
#保持实体类
-keep public class **.*Model*.** {*;}
1
2
3
4
5
6
7
8
9
10
11
12
# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
<init>(Java.lang.Throwable);
}
#jni方法不混淆
-keepclasseswithmembernames class * { # 保持native方法不被混淆
native <methods>;
}
-keepclassmembers enum * {
public static ** values();
public static ** valueOf(java.lang.String);
}

2、针对 APP 的量身定制

这部分主要是对实体类及其成员、内部类、WebView、JavaScript、反射的一些处理,针对APP做具体的代码混淆。

3、针对第三方 jar 包的解决方案

一般来说,第三方都会给出自己的混淆配置,拿过来就行

1
2
3
4
5
6
7
8
9
10
#okhttp3混淆配置
-dontwarn okhttp3.**
-keep class okhttp3.** { *;}
-dontwarn okio.**
#EventBus混淆配置
-keepattributes *Annotation*
-keepclassmembers class ** {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }

遇到的坑:

在做一个项目的时候,发现代码混淆之后,打开 APP,直接ANR崩溃,反复查看逻辑,最后发现是SharePerference 的坑,SharePerference 所依赖的 Activity 没有 keep,导致 App 运行时找不到,有两种方案,一种是 keep SP 所在的 Activity,或者 SP 使用生命周期更长的Application.
还有就是小伙伴使用 360 加固,导致使用 Activity.SharePerference 直接崩。加固有风险,使用需谨慎,有时候,还是手动混淆比较方便一点,混淆关键代码就行了。

世界再嘈杂,匠人的内心,绝对是必须是安静的、安定的。