当您使用 Android Gradle 插件 3.4.0 或更高版本构建项目时,该插件不再使用 ProGuard 执行编译时代码优化,而是与 R8 编译器协同工作,处理以下编译时任务:
- 代码缩减(即摇树优化):从应用及其库依赖项中检测并安全地移除不使用的类、字段、方法和属性(这使其成为了一个对于规避 64k 引用限制非常有用的工具)。例如,如果您仅使用某个库依赖项的少数几个 API,缩减功能可以识别应用不使用的库代码并仅从应用中移除这部分代码。如需了解详情,请转到介绍如何缩减代码的部分。
- 资源缩减:从封装应用中移除不使用的资源,包括应用库依赖项中不使用的资源。此功能可与代码缩减功能结合使用,这样一来,移除不使用的代码后,也可以安全地移除不再引用的所有资源。如需了解详情,请转到介绍如何缩减资源的部分。
- 混淆处理:缩短类和成员的名称,从而减小 DEX 文件的大小。如需了解详情,请转到介绍如何对代码进行混淆处理的部分。
-
优化:检查并重写代码,以进一步减小应用的 DEX 文件的大小。例如,如果 R8 检测到从未采用过给定 if/else 语句的
else {}
分支,则会移除else {}
分支的代码。如需了解详情,请转到介绍代码优化的部分。
启用缩减、混淆处理和优化功能
当您使用 Android Studio 3.4 或 Android Gradle 插件 3.4.0 及更高版本时,R8 是默认编译器,用于将项目的 Java 字节码转换为在 Android 平台上运行的 DEX 格式。不过,当您使用 Android Studio 创建新项目时,缩减、混淆处理和代码优化功能默认处于停用状态。这是因为,这些编译时优化功能会增加项目的构建时间,而且如果您没有充分自定义要保留的代码,还可能会引入错误。
因此,在构建应用的最终版本(也就是在发布应用之前测试的版本)时,最好启用这些编译时任务。如需启用缩减、混淆处理和优化功能,请在项目级 build.gradle
文件中添加以下代码。
android {
buildTypes {
release {
// Enables code shrinking, obfuscation, and optimization for only
// your project's release build type.
minifyEnabled true
// Enables resource shrinking, which is performed by the
// Android Gradle plugin.
shrinkResources true
// Includes the default ProGuard rules files that are packaged with
// the Android Gradle plugin. To learn more, go to the section about
// R8 configuration files.
proguardFiles getDefaultProguardFile(
'proguard-android-optimize.txt'),
'proguard-rules.pro'
}
}
...
}
如需输出 R8 在构建项目时应用的所有规则的完整报告,请将以下代码添加到模块的 proguard-rules.pro 文件中:
// You can specify any path and filename.
-printconfiguration ~/tmp/full-r8-config.txt
添加其他配置
当您使用 Android Studio 创建新项目或模块时,IDE 会创建一个 <module-dir>/proguard-rules.pro 文件,以便您添加自己的规则。此外,您还可以通过将相应文件添加到模块的 build.gradle 文件的 proguardFiles 属性中,从其他文件添加额外的规则。
例如,您可以通过在相应的 productFlavor 代码块中再添加一个 proguardFiles 属性来添加每个构建变体专用的规则。以下 Gradle 文件会将 flavor2-rules.pro 添加到 flavor2 产品变种中。现在,flavor2 使用全部三个 ProGuard 规则,因为还应用了来自 release 代码块的规则。
android {
...
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile(
'proguard-android-optimize.txt'),
// List additional ProGuard rules for the given build type here. By default,
// Android Studio creates and includes an empty rules file for you (located
// at the root directory of each module).
'proguard-rules.pro'
}
}
flavorDimensions "version"
productFlavors {
flavor1 {
...
}
flavor2 {
proguardFile 'flavor2-rules.pro'
}
}
}
缩减代码
如果将 minifyEnabled
属性设为 true
,系统会默认启用 R8 代码缩减功能。
代码缩减(也称为“摇树优化”)是指移除 R8 确定在运行时不需要的代码的过程。此过程可以大大减小应用的大小,例如,当您的应用包含许多库依赖项,但只使用它们的一小部分功能时。
为了缩减应用的代码,R8 首先会根据组合的配置文件集确定应用代码的所有入口点。这些入口点包括 Android 平台可用来打开应用的 Activity 或服务的所有类。从每个入口点开始,R8 会检查应用的代码来构建一张图表,列出应用在运行时可能会访问的所有方法、成员变量和其他类。系统会将与该图表没有关联的代码视为执行不到的代码,并可能会从应用中移除该代码。
图 1 显示了一个具有运行时库依赖项的应用。R8 通过检查应用的代码,确定可以从 MainActivity.class
入口点执行到的 foo()
、faz()
和 bar()
方法。不过,您的应用从未在运行时使用过 OkayApi.class
类或其 baz()
方法,因此 R8 会在缩减应用时移除该代码。
自定义要保留的代码
在大多数情况下,如要让 R8 仅移除不使用的代码,使用默认的 ProGuard 规则文件 (proguard-android- optimize.txt
) 就已足够。不过,在某些情况下,R8 很难做出正确判断,因而可能会移除应用实际上需要的代码。下面列举了几个示例,说明它在什么情况下可能会错误地移除代码:
- 当应用通过 Java 原生接口 (JNI) 调用方法时
- 当您的应用在运行时查询代码时(如使用反射)
通过测试应用应该可以发现因错误移除代码而导致的错误,但您也可以通过生成已移除代码的报告检查移除了哪些代码。
如需修复错误并强制 R8 保留某些代码,请在 ProGuard 规则文件中添加 -keep
代码行。例如:
-keep public class MyClass
或者,您也可以为要保留的代码添加 @Keep
注释。在类上添加 @Keep
可按原样保留整个类。在方法或字段上添加该注释,将使该方法/字段(及其名称)以及类名称保持不变。请注意,只有在使用 AndroidX 注释库且您添加 Android Gradle 插件随附的 ProGuard 规则文件时,此注释才可用。有关详情,请参阅介绍如何启用缩减功能的部分
在使用 -keep
选项时,有许多注意事项;如需详细了解如何自定义规则文件,请参阅 ProGuard 手册。问题排查部分简要介绍了移除代码后您可能会遇到的其他常见问题。
解码经过混淆处理的堆栈轨迹
R8 对代码进行混淆处理后,理解堆栈轨迹的难度将会极大增加,因为类和方法的名称可能有变化。除了重命名之外,R8 还可能会更改出现在堆栈轨迹中的行号,以便在写入 DEX 文件时进一步缩减大小。幸运的是,R8 在每次运行时都会创建一个 mapping.txt 文件,其中列出了经过混淆处理的类、方法和字段名称与原始名称的映射关系。此映射文件还包含用于将行号映射回原始源文件行号的信息。R8 会将此文件保存在 <module- name>/build/outputs/mapping/<build-type>/ 目录中。
在 Google Play 上发布应用时,您可以上传每个 APK 版本对应的 mapping.txt
文件。然后,Google Play 会根据用户报告的问题对传入的堆栈轨迹进行去混淆处理,以便您可以在 Google Play 管理中心查看这些堆栈轨迹。如需了解详情,请参阅介绍如何对崩溃堆栈轨迹进行去混淆处理的帮助中心文章。
网友评论