点赞关注,再也不迷路,你的支持对我意义重大!android
🔥 Hi,我是丑丑。本文 GitHub · Android-NoteBook 已收录,这里有 Android 进阶成长路线笔记 & 博客,欢迎跟着彭丑丑一块儿成长。(联系方式在 GitHub)git
代码混淆对于每一个入门的 Android 工程师来讲都不会太陌生,由于在编译正式版本时,这是一个必不可少的过程。并且使用代码混淆也至关简单,简单到只须要配置一句minifyEnabled true
。可是你是否理解混淆的原理,若是问你代码混淆到底作了什么,你会怎么说?github
若是以混淆编译器来划分的话,Android 代码混淆能够分为如下两个时期:web
下图梳理了它们随着 Android Gradle Plugin 版本迭代相应作出的变动:markdown
其中,混淆编译器的变动:架构
其中:DEX编译器的变动:app
若是须要修正 Android Gradle Plugin 的默认行为,能够在gradle.properties
中添加配置:工具
# 显式启用 R8
android.enableR8 = true
复制代码
# 1. 只对 Android Library module 停用 R8 编译器
android.enableR8.libraries = false
# 2. 对全部 module 停用 R8 编译器
android.enableR8 = false
复制代码
# 显式启用 D8
android.enableD8 = true
复制代码
# 显式禁用 D8
android.enableD8 = false
复制代码
另外,若是在应用模块的 build.gradle
文件中设置useProguard = false
,也会使用 R8 编译器代替 ProGuard。oop
ProGuard 与 R8 都提供了压缩(shrinker)、优化(optimizer)、混淆(obfuscator)、预校验(preverifier)四大功能:组件化
压缩(也称为摇树优化,tree shaking):从 应用及依赖项 中移除 未使用 的类、方法和字段,有助于规避 64 方法数的瓶颈
优化:经过代码 分析 移除更多未使用的代码,甚至重写代码
混淆:使用无心义的简短名称 重命名 类/方法/字段,增长逆向难度
预校验:对于面向 Java 6 或者 Java 7 JVM 的 class 文件,编译时能够把 预校验信息 添加到类文件中(StackMap 和 StackMapTable属性),从而加快类加载效率。预校验对于 Java 7 JVM 来讲是必须的,可是对于 Android 平台 无效
使用 ProGuard 时,部分编译流程以下图所示:
使用 R8 时,部分编译流程以下图所示:
对比如下 ProGuard 与 R8 :
一、开源
二、R8 支持全部现有 ProGuard 规则文件
三、都提供了四大功能:压缩、优化、混淆、预校验
一、ProGuard 可用于 Java 项目,而 R8 专为 Android 项目设计
二、R8 将脱糖(Desugar)、压缩、优化、混淆和 dex(D8 编译器)整合到一个步骤中,显着提升了编译性能
关于 D8 编译器
将 .class 字节码转化为 .dex 字节码的过程被称为 DEX 编译,最初是由DX 编译器完成。与 DX 编译器相比,新的 D8 编译器的编译速度 更快,输出的 .dex 文件 更小 ,却能保持相同乃至 更出色 的应用运行时性能
不管使用 R8 仍是 ProGuard,默认不会启用压缩、优化和混淆功能。 这个设计主要是出于两方面考虑:一方面是由于这些编译时任务会增长编译时间,另外一方面是由于若是没有充分定义混淆保留规则,还可能会引入运行时错误。所以,最好 只在应用的测试版本和发布版本中启用这些编译时任务,参考使用示例:
// build.gradle
...
android {
buildTypes {
// 测试版本
preview {
// 启用代码压缩、优化和混淆(由R8或者ProGuard执行)
minifyEnabled true
// 启用资源压缩(由Android Gradle plugin执行)
shrinkResources true
// 指定混淆保留规则文件
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
// 发布版本
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
// 开发版本
debug{
minifyEnabled false
}
}
...
}
复制代码
minifyEnabled
:(默认状况下)启用代码压缩、优化、混淆与预校验shrinkResources
:启用资源压缩proguardFiles
、proguardFile
:指定 ProGuard 规则文件,前者能够指定多个参数。下面两段配置的做用是同样的。 // 方式一:
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
// 方式二:
proguardFile getDefaultProguardFile('proguard-android-optimize.txt')
proguardFile 'proguard-rules.pro'
复制代码
前面提到了:不管使用R8仍是ProGuard,压缩、优化和混淆功能都是 默认关闭的。经过如下配置能够灵活控制:
minifyEnabled false
// 这行就没有效果了
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
复制代码
-dontshrink
复制代码
-dontoptimize
复制代码
注意:R8 不能关闭优化,也不容许修改优化的行为,事实上,R8 会忽略修改默认优化行为的规则。例如设置 -optimizations
和 -optimizationpasses
后会获得编译时警告:
AGPBI: {"kind":"warning","text":"Ignoring option: -optimizations","sources":[{"file":"省略..."}],"tool":"D8"}
AGPBI: {"kind":"warning","text":"Ignoring option: -optimizationpasses","sources":"省略..."}],"tool":"D8"}
复制代码
-dontobfuscate
复制代码
-dontpreverify
复制代码
R8 延续了 ProGuard 使用规则文件修改默认行为的作法。在不少时候,规则文件也被称为混淆保留规则文件,这是由于该文件内定义的绝大多数规则都是和代码混淆相关的。事实上,文件内还能够定义代码压缩、优化和预校验规则,所以称为 ProGuard 规则文件比较严谨。
在上一节里,咱们提到了使用proguardFiles
和proguardFile
指定 ProGuard 规则文件。对于任何一个项目,它的 ProGuard 规则文件有如下三种来源:
在编译时,Android Gradle 插件会生成 proguard-android-optimize.txt
、 proguard-android.txt
,位置在<module-dir>/build/intermediates/proguard-files/
。这两个文件中除了注释以外,惟一的区别是前者启用了以下代码压缩,然后者关闭了代码压缩,以下所示:
# proguard-android-optimize.txt
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
-optimizationpasses 5
-allowaccessmodification
相同部分省略...
复制代码
# proguard-android.txt
-dontoptimize
相同部分省略...
复制代码
其中相同的那部分混淆规则中,下面这一部分是比较特殊的:
-keep class android.support.annotation.Keep
-keep class androidx.annotation.Keep
// 保留@Keep注解的类,保留...TODO
-keep @android.support.annotation.Keep class * {*;}
-keep @androidx.annotation.Keep class * {*;}
// 保留@Keep修饰的方法
-keepclasseswithmembers class * {
@android.support.annotation.Keep <methods>;
}
-keepclasseswithmembers class * {
@androidx.annotation.Keep <methods>;
}
// 保留@Keep修饰的字段
-keepclasseswithmembers class * {
@android.support.annotation.Keep <fields>;
}
-keepclasseswithmembers class * {
@androidx.annotation.Keep <fields>;
}
// 保留@Keep修饰的构造方法
-keepclasseswithmembers class * {
@android.support.annotation.Keep <init>(...);
}
-keepclasseswithmembers class * {
@androidx.annotation.Keep <init>(...);
}
复制代码
它指定了与@Keep
注解相关的全部保留规则,这里就解释了为何使用@Keep
修饰的成员不会被混淆了吧?
在编译时,AAPT2 会根据对 Manifest 中的类、布局及其余应用资源的引用来生成aapt_rules.txt
,位置在<module-dir>/build/intermediates/proguard-rules/debug/aapt_rules.txt
。 例如,AAPT2 会为 Manifest 中注册的每一个组件添加保留规则:
Referenced at [项目路径]/app/build/intermediates/merged_manifests/release/AndroidManifest.xml:19
-keep class com.have.a.good.time.MainActivity { <init>(); }
省略...
复制代码
在这里,AAPT2 生成了MainActivity
的保留规则,同时它还指出了引用出处:AndroidManifest.xml:19
。这是由于 启动 Activity 的过程当中,须要使用反射的方式实例化具体的每个 Activity ,有兴趣能够看下 ActivityThread#performLaunchActivity()
-> Instrumentation#newActivity()
建立新 Module 时,IDE 会在该模块的根目录中建立一个 proguard-rules.pro
文件。固然,除了这个自动生成的文件,还能够按需建立额外的规则文件。例如,下面的配置对 release 添加了额外的规则文件:
...
android {
...
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
productFlavors {
dev{
...
}
release{
proguardFile 'release-rules.pro'
}
}
}
...
复制代码
小结一下:
规则文件来源 | 描述 |
---|---|
Android Gradle 插件 | 在编译时,由 Android Gradle 插件生成 |
AAPT2 | 在编译时,AAPT2 根据对应用清单中的类、布局及其余应用资源的引用来生成保留规则 |
Module | 建立新 Module 时,由 IDE 建立,或者另外按需建立 |
若是将 minifyEnabled
属性设为 true
,ProGuard 或 R8 会未来自上面列出的全部可用来源的规则组合在一块儿。为了看到完整的规则文件,能够在proguard-rules.pro
中添加如下配置,输出编译项目时应用的全部规则的完整报告:
-printconfiguration build/intermediates/proguard-files/full-config.txt
复制代码
在组件化的项目中,须要注意应用 Module 和 Library Module 的行为差别和组件化的资源汇合规则,总结为如下几个重点:
使用较高版本的 Android Gradle Plugin,不会将汇总的资源放置在 exploded-aar
文件夹。即使如此,Lib Module 的资源汇总到 App Module 的规则是同样的。
咱们经过一个简单示例测试不一样配置下的混淆结果:
配置一 | 配置二 | 配置三 | 配置四 | |
---|---|---|---|---|
App Module 开启混淆 | X | X | √ | √ |
Base Module 开启混淆 | X | √ | X | √ |
将构建的 apk 包拖到 Android Studio 面板上便可分析 Base 类混淆结果,例如配置一的结果:
所有测试结果以下:
配置一 | 配置二 | 配置三 | 配置四 | |
---|---|---|---|---|
App Module 开启混淆 | X | X | √ | √ |
Base Module 开启混淆 | X | √ | X | √ |
(结果)Base 类是否被混淆 | X | X | √ | √ |
能够看到,混淆开启由 App Module 决定, 与Lib Module 无关。
如今咱们分别在 Lib Module 和 App Module 的 proguard-rules.pro
中添加 Base 类的混淆保留规则,并在 build.gradle
中添加配置文件,测试 Base 类是否能保留:
-keep class com.rui.base.Base
复制代码
测试结果以下:
配置位置 | Lib Module | App Module |
---|---|---|
(结果)Base 类是否保留 | X | √ |
能够看到:(默认状况)混淆规则以 App Module 中的混淆规则文件为准。
这里就引入两种主流的组件化混淆方案:
这种方案将混淆规则都放置到 App Module 的proguard-rules.pro
中,最简单也最直观,缺点是移除 Lib Module 时,须要从 App Module 中移除相应的混淆规则。尽管多余的混淆规则并不会形成编译错误或者运行错误,但仍是会影响编译效率。
不少的第三方 SDK,就是采用了这种组件化混淆方案。在 App Module 中添加依赖的同时,也须要在proguard-rules.pro
中添加专属的混淆规则,这样才能保证release
版本正常运行。
这种方案将专属的混淆规则设置到 Lib Module 的proguard-rules.pro
,可是根据前面的测试,在 Lib Module 中设置的混淆规则是不生效的。为了让规则生效,还须要在 Lib Module 的build.gradle
中添加如下配置:
...
android{
defaultConfig{
consumerProguardFiles 'consumer-rules.pro'
}
}
复制代码
其中consumer-rules.pro
文件:
-keep class com.rui.base.Base
复制代码
测试结果代表,Base 类已经被保留了。这种使用consumerProguardFiles
的方式有如下几个特色:
consumerProguardFiles
只对 Lib Module 生效,对 App Module 无效consumerProguardFiles
会将混淆规则输出为proguard.txt
文件,并打包进 aar 文件proguard.txt
汇总为最终的混淆规则,这一点能够经过前面提到的-printconfiguration
证实ProGuard 是 Java 字节码优化工具,而 R8 是专为 Android 设计的,编译性能和编译产物更优秀;
ProGuard 与 R8 都提供了四大功能:压缩、优化、混淆和预校验。ProGuard 主要是对 .class 文件执行代码压缩、优化与混淆,再由 D8 编译器执行脱糖并转换为 .dex 文件。R8 将压缩、优化、混淆、脱糖和 dex 整合为一个步骤;
ProGuard 规则文件有三种来源:Android Gradle 插件、AAPT二、Module;
默认状况下,混淆规则以 App Module 中的混淆规则文件为准,使用 consumer-rules.pro 文件能够设置 Lib Module 专属混淆规则。
创做不易,你的「三连」是丑丑最大的动力,咱们下次见!