混淆是打包过程中最重要的流程之一,在没有特殊原因的情况下,所有 app 都应该开启混淆。首先,这里说的的混淆其实是包括了代码压缩、代码混淆以及资源压缩等的优化过程。依靠 ProGuard,混淆流程将主项目以及依赖库中未被使用的类、类成员、方法、属性移除,这有助于规避64K方法数的瓶颈;同时,将类、类成员、方法重命名为无意义的简短名称,增加了逆向工程的难度。而依靠 Gradle 的 Android 插件,我们将移除未被使用的资源,可以有效减小 apk 安装包大小。
###混淆简介
Android中的“混淆”可以分为两部分,一部分是 Java 代码的优化与混淆,依靠 proguard 混淆器来实现;另一部分是资源压缩,将移除项目及依赖的库中未被使用的资源(资源压缩严格意义上跟混淆没啥关系,但一般我们都会放一起讲)。
1. 代码压缩
代码混淆是包含了代码压缩、优化、混淆等一系列行为的过程。混淆过程会有如下几个功能:压缩。移除无效的类、类成员、方法、属性等;
优化。分析和优化方法的二进制代码;根据proguard-android-optimize.txt中的描述,优化可能会造成一些潜在风险,不能保证在所有版本的Dalvik上都正常运行。
混淆。把类名、属性名、方法名替换为简短且无意义的名称;
预校验。添加预校验信息。这个预校验是作用在Java平台上的,Android平台上不需要这项功能,去掉之后还可以加快混淆速度。
这四个流程默认开启。
在 Android 项目中我们可以选择将“优化”和“预校验”关闭,对应命令是-dontoptimize、-dontpreverify(当然,默认的 proguard-android.txt 文件已包含这两条混淆命令,不需要开发者额外配置)。
2. 资源压缩
资源压缩将移除项目及依赖的库中未被使用的资源,这在减少 apk 包体积上会有不错的效果,一般建议开启。具体做法是在 build.grade 文件中,将 shrinkResources 属性设置为 true。需要注意的是,只有在用minifyEnabled true开启了代码压缩后,资源压缩才会生效。资源压缩包含了“合并资源”和“移除资源”两个流程。
“合并资源”流程中,名称相同的资源被视为重复资源会被合并。需要注意的是,这一流程不受shrinkResources属性控制,也无法被禁止, gradle 必然会做这项工作,因为假如不同项目中存在相同名称的资源将导致错误。gradle 在四处地方寻找重复资源:
src/main/res/ 路径
不同的构建类型(debug、release等等)
不同的构建渠道
项目依赖的第三方库
合并资源时按照如下优先级顺序:
依赖 -> main -> 渠道 -> 构建类型
举个例子,假如重复资源同时存在于main文件夹和不同渠道中,gradle 会选择保留渠道中的资源。
同时,如果重复资源在同一层次出现,比如src/main/res/ 和 src/main/res/,则 gradle 无法完成资源合并,这时会报资源合并错误。
“移除资源”流程则见名知意,需要注意的是,类似代码,混淆资源移除也可以定义哪些资源需要被保留。
eclipse下的混淆:
在project.properties中将11行改成proguard.config=proguard-project.txt将前面的#去掉,复制sdk/tools/proguard/下的proguard-android.txt到工程的根目录下。
或者使用proguard-project.txt这个包也可以。
proguard.config=proguard-project.txt
我之前在设置混淆的时候出现了警告信息,设置忽略警告。
-dontwarn com.unionpay.**
设置某一个包下的所有class文件不混淆
-keep class com.unionpay.** {*;}
一般一起使用
-dontwarn com.unionpay.**
-keep class com.unionpay.** {*;}
android studio下的混淆
####1. 混淆配置一般情况下,app module 的 build.gradle 文件默认会有如下结构:
android {
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
}
因为开启混淆会使编译时间变长,所以debug模式下不应该开启。
开启混淆,我们需要做的是:
1. 将release下minifyEnabled的值改为true,打开混淆
2. 加上shrinkResources true,打开资源压缩。
修改后文件内容如下:
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
#### 2. 自定义混淆规则
###### 指定压缩级别
-optimizationpasses 5
##### 不跳过非公共的库的类成员
-dontskipnonpubliclibraryclassmembers
##### 混淆时采用的算法
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
##### 把混淆类中的方法名也混淆了
-useuniqueclassmembernames
##### 优化时允许访问并修改有修饰符的类和类的成员
-allowaccessmodification
##### 保留行号
-keepattributes SourceFile,LineNumberTable
##### 保持所有实现 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();
}
##### Fragment不需要在AndroidManifest.xml中注册,需要额外保护下
-keep public class * extends android.support.v4.app.Fragment
-keep public class * extends android.app.Fragment
##### 保持测试相关的代码
-dontnote junit.framework.**
-dontnote junit.runner.**
-dontwarn android.test.**
-dontwarn android.support.test.**
-dontwarn org.junit.**
常用的自定义混淆规则
不混淆某个类
-keep public class name.xxxx.example.Test { *; }
不混淆某个包所有的类
-keep class name.huihui.test.** { *; }
不混淆某个类的子类
-keep public class * extends name.xxxx.example.Test { *; }
不混淆所有类名中包含了“model”的类及其成员
-keep public class **.*model*.** {*;}
不混淆某个接口的实现
-keep class * implements name.xxxx.example.TestInterface { *; }
不混淆某个类的构造方法
-keepclassmembers class name.xxxx.example.Test {
public <init>();
}
不混淆某个类的特定的方法
-keepclassmembers class name.xxxx.example.Test {
public void test(java.lang.String);
}
#保护注解
-keepattributes *Annotation*
#保持所有拥有本地方法的类名及本地方法名
-keepclasseswithmembernames class * {
native <methods>;
}
# 保持自定义View的get和set相关方法
-keepclassmembers public class * extends android.view.View {
void set*(***);
*** get*();
}
##### 检查混淆结果
混淆过的包必须进行检查,避免因混淆引入的bug。
一方面,需要从代码层面检查。使用上文的配置进行混淆打包后在 <module-name>/build/outputs/mapping/release/ 目录下会输出以下文件:
dump.txt
描述APK文件中所有类的内部结构
mapping.txt
提供混淆前后类、方法、类成员等的对照表
seeds.txt
列出没有被混淆的类和成员
usage.txt
列出被移除的代码:
我们可以根据 seeds.txt 文件检查未被混淆的类和成员中是否已包含所有期望保留的,再根据 usage.txt 文件查看是否有被误移除的代码。
另一方面,需要从测试方面检查。将混淆过的包进行全方面测试,检查是否有 bug 产生。