提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
Android 应用在发布时一般都会做代码混淆,具体就是在 build.gradle 中配置
// 缩减代码
minifyEnabled true
作用一是增加反编译的难度,二是缩减应用大小,下面来看一下混淆的具体原理
以下是本篇文章正文内容
一、ProGuard、DX、D8、R8
在上面 Android 打包流程图中会把项目和依赖库中的 Class 文件经过 dex 编译器编译为 dex 文件,在编译之前会先经过混淆和脱糖
在 Android gradle plugin 3.4.0 之前默认编译过程是这样的,D8 是新一代的 dex 编译器在 3.1.0 之前是 DX 编译器
在 Android gradle plugin 3.4.0 及更高版本是这样的,R8 集成了混淆和 dex 编译器 D8
- ProGuard 压缩、优化和混淆 Java 字节码文件的免费工具
- R8 专为 Android 项目设计支持所有现有 ProGuard 规则文件
- DX 老版本的 dex 编译器
- D8 执行脱糖,并将 class 文件转换为 dex 文件,与老牌编译器 DX 相比,D8 运行得更快,生成的 dex 文件更小,运行时性能相当或更好
混淆在 AGP3.2.0 及之前是 ProGuard ,3.2.0 支持 R8,3.4.0 及之后默认使用 R8
dex 编译器在 AGP3.0.0 及之前使用 DX,3.0.0 支持 D8,3.1.0 及之后默认使用 D8
二、缩减、混淆处理和优化应用
混淆这个叫法其实并不准确,因为开启混淆之后 R8 做了以下几件事:
- 代码缩减(即摇树优化):从应用及其库依赖项中检测并安全地移除不使用的类、字段、方法和属性(这使其成为了一个对于规避 64k 引用限制非常有用的工具)。例如,如果您仅使用某个库依赖项的少数几个 API,那么缩减功能可以识别应用不使用的库代码并仅从应用中移除这部分代码
- 从封装应用中移除不使用的资源,包括应用库依赖项中不使用的资源。此功能可与代码缩减功能结合使用,这样一来,移除不使用的代码后,也可以安全地移除不再引用的所有资源
- 缩短类和成员的名称,从而减小 DEX 文件的大小
- 检查并重写代码,以进一步减小应用的 DEX 文件的大小。例如,如果 R8 检测到从未采用过给定 if/else 语句的 else {} 分支,则会移除 else {} 分支的代码
三、ProGuard 规则文件来源
R8 延续了 ProGuard 使用规则文件修改默认行为的做法。在很多时候,规则文件也被称为混淆保留规则文件,这是因为该文件内定义的绝大多数规则都是和代码混淆相关的。事实上,文件内还可以定义代码压缩、优化和预校验规则,因此称为 ProGuard 规则文件比较严谨
上面一段文字说明来自参考&感谢
ProGrard 规则文件有多个来源:
来源 | 位置 | 说明 |
---|---|---|
Android Studio | <module-dir>/proguard-rules.pro | 当使用 Android Studio 创建新模块时,Android Studio 会在该模块的根目录中创建 proguard-rules.pro 文件。默认情况下此文件不会应用任何规则,应在此添加自己的proguard 规则 |
Android Gradle 插件 | 由 Android Gradle 插件在编译时生成 | Android Gradle 插件会生成 proguard-android-optimize.txt(其中包含了对大多数 Android 项目都有用的规则)它指定了与 @Keep 注解相关的所有保留规则,这里就解释了为什么使用 @Keep 修饰的成员不会被混淆了 |
依赖库 | AAR: <library-dir>/proguard.txt JAR: <library-dir>/META-INF/proguard/ | 如果某个 AAR 库是使用它自己的 ProGuard 规则文件发布的,并且将该 AAR 库作为编译时依赖项纳入到项目中,那么 R8 在编译项目时会自动应用其规则 |
AAPT2 | <module-dir>/build/intermediates/aapt-proguard-rules/${变体-buildTypes}/aapt_rules.txt | AAPT2 会根据对应用清单中的类、布局及其他应用资源的引用,生成保留规则。例如,AAPT2 会为您在应用清单中注册为入口点的每个 Activity 添加一个保留规则(因为 Activity 的实例都是通过反射创建的) |
另外也可以通过 proguardFile 给不同的变体配置不同的规则文件
productFlavors {
flavor1 {
...
}
flavor2 {
proguardFile 'flavor2-rules.pro'
}
}
R8 会将来自上述所有可用来源的规则组合在一起,将以下代码添加到模块的 proguard-rules.pro 文件中可输出 R8 在构建项目时应用的所有规则的完整报告
// 输出路径和文件名可以更改
-printconfiguration build/intermediates/proguard-files/full-r8-config.txt
我测试在开启混淆后 Android Gradle 插件会生成 proguard-android.txt、proguard-defaults.txt、proguard-android-optimize.txt 三个文件,其中 defaults 与 android-optimize 一样都开启了代码优化 android 没有开启代码优化,选择其中一个使用就可以
四、模块混淆
不管 Module 的 minifyEnabled 设置 true 或 false 在 proguard-rules.pro 中配置的混淆规则都不生效,只有通过 consumerProguardFiles 配置的混淆规则才生效并且最后会合并到整个 App 的混淆规则中对整个项目生效
defaultConfig {
consumerProguardFiles "consumer-rules.pro"
}
上面是我的测试结果跟 ‘参考&感谢’ 中的结论不一致
五、异常堆栈信息去混淆
项目开启混淆后发生 crash 时堆栈信息也是混淆过的不方便排查定位,这个时候就需要对混淆信息去混淆,首先在混淆规则文件里定义:
# 输出 mapping.txt 文件
-printmapping ./build/outputs/mapping/mapping.txt
然后在 SDK 路径下 android-sdk/tools/proguard/bin/ 下有 proguard.sh 和 proguardgui.sh 前者是命令行方式后者是用户界面的方式,第一种:
SDK 路径/tools/proguard/bin/retrace.sh <mapping.txt 文件路径> <异常堆栈文件.txt路径>
之后就可以得到去混淆后的堆栈信息,第二种双击 proguardgui.sh 选择 mapping.txt 文件路径输入异常堆栈信息点击 retrace 即可
六、混淆字典
混淆后默认使用英文字母替换源代码但也可以通过定位混淆字典把代码混淆我其他特殊字符或者是汉字,配置混淆字典:
# 混淆字典
-obfuscationdictionary ../proguard-dictionary.txt
-classobfuscationdictionary ../proguard-dictionary.txt
-packageobfuscationdictionary ../proguard-dictionary.txt
通过反编译混淆后的 apk 即可以看到效果,以下是几个混淆字典开源库:
ProguardDictionary
ProguardDictionaryGenerator
FrenziedProguard
上面两部分内容使用公司项目测试所以没有添加截图
七、资源压缩
通过以下配置即可开启资源压缩,开启资源压缩时需要先开启代码压缩,因为在移除不需要的代码后才可以确定哪些资源也是不需要的
// 资源压缩
shrinkResources true
如果有要保留和舍弃的特定资源文件,可以在项目中创建一个包含 <resources> 标记的 XML 文件,并在 tools:keep 属性中指定每个要保留的资源,在 tools:discard 属性中指定每个要舍弃的资源。这两个属性都接受以逗号分隔的资源名称列表,可以将星号字符用作通配符
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
tools:discard="@layout/unused2" />
默认情况下资源压缩可以准确的判断是否使用了某个资源,如果代码中使用了 Resources.getIdentifier() 动态生成字符串查找资源,资源收缩器会把名称匹配的资源标记为已使用不删除。例如,以下代码会将所有带 img_ 前缀的资源标记为已使用
val name = String.format("img_%1d", angle + 1)
val res = resources.getIdentifier(name, "drawable", packageName)
可以通过在上面定义保留和舍弃资源的 xml 文件中将 shrinkMode 设为 strict 指定资源缩减器只保留确定要使用的资源
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:shrinkMode="strict" />
如果启用了严格缩减模式,并且代码通过动态生成的字符串引用资源,必须使用 tools:keep 属性手动保留这些资源
八、保留指定资源
默认情况下 Gradle 会将所有尺寸所有语言等各种维度的资源全部打入 APK 增大了 APK 体积,同一类资源通常会选择性的打入 APK 以减少包体积,例如:
android {
defaultConfig {
// ...
resConfigs "zh", "xxhdpi", "ldltr", "desk"
}
}
有些依赖库包含了很多其它非中文资源,所以可以添加了 zh 配置用于只保留中文资源
添加 xxhdpi 配置用于只保留一套图片资源,例如如果有 xhdpi 和 xxhdpi 的两套图片,会只保留 xxhdpi 的图片,如果有 hdpi 和 xhdpi 两套图片,会只保留 xhdpi 的图片
ldltr 指定方向性资源上只包含左到右的资源文件
如果应用没有适配 watch 手表,可以通过添加 desk 配置将 watch 相关资源移除
上面这一部分内容没有验证
九、AndResGuard
Android 本身只支持资源压缩(移除无用资源)不支持资源混淆,这部分可以使用 AndResGuard 来完成,它的原理就是创建了一个资源混淆打包任务,该任务会先调用默认的打包任务,在默认打包工作结束后,会解压打好的 apk 包,识别解析包里的 resources.arsc 资源表,然后再混淆 res 文件夹下面的所有资源文件,同时相对应的修改资源表,最后将修改后的资源重新打包签名,生成新的 apk 包。
参考与感谢
缩减、混淆处理和优化应用
Android Gradle 插件版本说明
补齐Android技能树 - 从害怕到玩转Android代码混淆
Android | 代码混淆到底做了什么?| 牛气冲天新年征文
58同城厂商内置包大小减少实战
采用AndResGuard进行资源混淆