在现代 Android 开发中,提供体积小、速度快且安全的应用是用户的基本期望。Android 构建系统实现此目的的主要工具是 R8 优化器,该编译器可处理死代码和资源移除(用于缩减大小)、代码重命名或缩减大小,以及应用优化。
启用 R8 是准备发布应用的关键步骤,但需要开发者以“保留规则”的形式提供指导。
阅读本文后,请观看 YouTube 上的 Performance Spotlight Week 视频,了解如何启用 R8 优化器、如何进行调试和问题排查。
为什么需要保留规则
之所以需要编写 Keep 规则,是因为存在一个核心冲突:R8 是一个静态分析工具,但 Android 应用通常依赖于动态执行模式,例如反射或使用 JNI(Java 原生接口)调用原生代码。
R8 通过分析直接调用来构建已使用代码的图表。当以动态方式访问代码时,R8 的静态分析无法预测到这一点,因此会将该代码标识为未使用并将其移除,从而导致运行时崩溃。
保留规则是向 R8 编译器发出的明确指令,内容为:“这个特定的类、方法或字段是一个入口点,将在运行时被动态访问。即使您找不到对它的直接引用,也必须保留它。”
如需详细了解保留规则,请参阅官方指南。
在哪里编写保留规则
应用的自定义保留规则会写入文本文件。按照惯例,此文件名为 proguard-rules.pro,位于应用或库模块的根目录中。然后,在模块的 build.gradle.kts 文件的 release build 类型中指定此文件。
release {
isShrinkResources = true
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
}使用正确的默认文件
getDefaultProguardFile 方法会导入 Android SDK 提供的一组默认规则。如果使用错误的文件,您的应用可能无法得到优化。请务必使用 proguard-android-optimize.txt。此文件提供了标准 Android 组件的默认保留规则,并 启用了 R8 的代码优化功能。过时的 proguard-android.txt 仅提供 Keep 规则,但不启用 R8 的优化。
由于这是一个严重的性能问题,因此从 Android Studio Narwhal 3 功能更新开始,我们会提醒开发者不要使用错误的文件。此外,从 Android Gradle 插件 9.0 版开始,我们将不再支持过时的 proguard-android.txt 文件。因此,请务必升级到优化版本。
如何编写保留规则
keep 规则由三个主要部分组成:
-
-keep或-keepclassmembers等选项 - 可选修饰符,例如
allowshrinking - 类规范,用于定义要匹配的代码
如需查看完整的语法和示例,请参阅添加保留规则指南。
保留规则反模式
了解最佳实践固然重要,但了解反模式也同样重要。这些反模式通常源于误解或问题排查捷径,可能会对正式版 build 的性能造成灾难性影响。
全局选项
这些标志是全局切换开关,绝不应在发布 build 中使用。它们仅用于临时调试,以隔离问题。
使用 -dontotptimize 会有效停用 R8 的性能优化,从而导致应用运行速度变慢。
使用 -dontobfuscate 时,您会停用所有重命名操作;使用 -dontshrink 时,您会关闭死代码移除功能。这两条全局规则都会增加应用大小。
请尽可能避免在生产环境中使用这些全局标志,以提供更高效的应用用户体验。
过于宽泛的保留规则
要让 R8 的优势化为乌有,最简单的方法就是编写过于宽泛的保留规则。如下所示的保留规则会指示 R8 优化器不要缩减、混淆或优化此软件包或其任何子软件包中的任何类。这会完全消除 R8 对整个软件包的优化效果。请尝试编写范围较窄且具体的保留规则。
-keep class com.example.package.** { *;} // WIDE KEEP RULES CAUSE PROBLEMS反转运算符 (!)
反转运算符 (!) 似乎是一种从规则中排除软件包的强大方法。但事情没那么简单。请看以下示例
-keep class !com.example.my_package.** { *; } // USE WITH CAUTION您可能会认为此规则意味着“不要保留 com.example.package 中的类”。但它实际上意味着“保留整个应用中不在 com.example.package 中的每个类、方法和属性”。如果您对此感到惊讶,最好检查 R8 配置中是否存在任何否定。
Android 组件的冗余规则
另一个常见错误是手动为应用的 Activities、Services 或 BroadcastReceivers 添加保留规则。这是不必要的。默认的 proguard-android-optimize.txt 文件已包含这些标准 Android 组件的相关规则,以便它们开箱即用。
此外,许多库都自带保留规则。因此,您无需自行编写这些规则。如果您使用的库中的 Keep 规则存在问题,最好与库作者联系,了解问题所在。
保留规则最佳实践
了解了不该做什么之后,我们再来谈谈最佳实践。
编写范围较窄的保留规则
好的保留规则应尽可能具体且范围尽可能小。它们应仅保留必要的内容,以便 R8 优化其他所有内容。
| 规则 | 质量 |
|---|---|
| 低:保留整个软件包及其子软件包 |
| 低:保留整个类,这可能仍然过于宽泛 |
-keepclassmembers class com.example.MyClass {
private java.lang.String secretMessage;
public void onNativeEvent(java.lang.String);
} | 高:仅保留特定类中的相关方法和属性 |
使用共同祖先实体
您可以编写一条针对通用基类或接口的规则,而不是为多个不同的数据模型编写单独的保留规则。以下规则指示 R8 保留实现此接口的类的所有成员,并且具有高度可伸缩性。
# Keep all fields of any class that implements SerializableModel
-keepclassmembers class * implements com.example.models.SerializableModel {
<fields>;
}使用注解定位多个类
创建自定义注解(例如 @Serialize),并使用它来“标记”需要保留其字段的类。这是另一种简洁、声明式且高度可扩缩的模式。您还可以为您正在使用的框架中的现有注解创建 Keep 规则。
# Keep all fields of any class annotated with @Serialize
-keepclassmembers class * {
@com.example.annotations.Serialize <fields>;
}选择合适的保留选项
保留选项是规则中最关键的部分。如果选择错误,可能会不必要地停用优化功能。
| 保留选项 | 作用 |
-keep | 防止移除或重命名声明中提及的类和成员。 |
-keepclassmembers | 防止移除或重命名指定成员,但允许移除类本身,不过仅限于未以其他方式移除的类。 |
-keepclasseswithmembers | 组合:仅当所有指定成员都存在时,才保留类及其成员。 |
如需详细了解保留选项,请参阅我们的保留选项文档。
允许使用调节系数进行优化
allowshrinking 和 allowobfuscation 等修饰符会放宽宽泛的 -keep 规则,从而将优化权限交还给 R8。例如,如果旧版库强制您对整个类使用 -keep,您可以通过允许缩减和混淆来恢复一些优化:
# Keep this class, but allow R8 to remove it if it's unused and allow R8 to rename it. -keep,allowshrinking,allowobfuscation class com.example.LegacyClass
添加全局选项以进行额外优化
除了 Keep 规则之外,您还可以向 R8 配置文件添加全局标志,以进一步优化。
-repackageclasses 是一个强大的选项,可指示 R8 将所有混淆后的类移至单个软件包中。这样可以移除冗余的软件包名称字符串,从而节省 DEX 文件中的大量空间。
-allowaccessmodification 允许 R8 扩大访问权限(例如,从 private 扩大到 public),以实现更积极的内联。现在,使用 proguard-android-optimize.txt 时,此功能默认处于启用状态。
警告:库作者绝不能将这些全局优化标志添加到其使用方规则中,因为这些标志会强制应用于整个应用。
为了让这一点更加明确,在 Android Gradle 插件 9.0 版中,我们将开始完全忽略库中的全局优化标志。
库的最佳实践
每个 Android 应用都会以某种方式依赖于库。接下来,我们来讨论一下库的最佳实践。
面向库开发者
如果您的库使用反射或 JNI,您有责任向其使用者提供必要的保留规则。这些规则会放置在 consumer-rules.pro 文件中,然后自动捆绑到库的 AAR 文件中。
android {
defaultConfig {
consumerProguardFiles("consumer-rules.pro")
}
...
}对于库使用方
滤除有问题的保留规则
如果您必须使用包含有问题的 Keep 规则的库,则可以从 AGP 9.0 开始在 build.gradle.kts 文件中过滤掉这些规则。这会指示 R8 忽略来自特定依赖项的规则。
release {
optimization.keepRules {
// Ignore all consumer rules from this specific library
it.ignoreFrom("com.somelibrary:somelibrary")
}
}最好的保留规则就是没有保留规则
终极 R8 配置策略是完全消除编写 Keep 规则的需求。对于许多应用,可以通过选择更倾向于代码生成而非反射的现代库来实现。通过代码生成,优化器可以更轻松地确定在运行时实际使用了哪些代码,以及可以移除哪些代码。此外,不使用任何动态反射意味着没有“隐藏”入口点,因此不需要保留规则。 选择新库时,请始终优先选择使用代码生成而非反射的解决方案。
如需详细了解如何选择库,请参阅明智地选择库。
调试 R8 配置并排查相关问题
当 R8 移除了本应保留的代码,或者 APK 大于预期时,请使用这些工具来诊断问题。
查找重复和全局 Keep 规则
由于 R8 会合并来自数十个来源的规则,因此很难知道“最终”规则集是什么。将此标志添加到 proguard-rules.pro 文件会生成一份完整报告:
# Outputs the final, merged set of rules to the specified file -printconfiguration build/outputs/logs/configuration.txt
您可以搜索此文件,找到冗余规则或将有问题的规则(如 -dontoptimize)追溯到包含该规则的特定库。
询问 R8:为什么要保留此内容?
如果预期要移除的类仍在应用中,R8 可以告诉您原因。只需添加以下规则:
# Asks R8 to explain why it's keeping a specific class class com.example.MyUnusedClass -whyareyoukeeping
在构建过程中,R8 会输出导致其保留该类的确切引用链,以便您跟踪引用并调整规则。
如需查看完整指南,请参阅排查 R8 问题部分。
后续步骤
R8 是一款强大的工具,可用于提升 Android 应用性能。其有效性取决于对它作为静态分析引擎的运行方式的正确理解。
通过编写具体的成员级规则、利用祖先和注解,以及谨慎选择正确的保留选项,您可以精确保留必要的内容。最先进的做法是选择基于代码生成的现代库,而不是基于反射的旧版库,从而完全消除对规则的需求。
在关注 Performance Spotlight Week 的同时,请务必观看 YouTube 上今天的 Spotlight Week 视频,并继续参与我们的 R8 挑战赛。如果您对启用或排查 R8 有任何疑问,请使用 #optimizationEnabled。我们随时为您提供帮助。
是时候亲自体验这些福利了。
我们建议您立即为应用启用 R8 完整模式。
- 请参阅我们的开发者指南,了解如何开始使用:启用应用优化。
- 检查您是否仍在使用
proguard-android.txt,并将其替换为proguard-android-optimize.txt。 - 然后,衡量影响。不要只是感觉到差异,还要验证差异。通过调整 GitHub 上的 Macrobenchmark 示例应用中的代码来衡量性能提升情况,以衡量之前和之后的启动时间。
我们相信,您会看到应用的性能有显著提升。
同时,您还可以使用社交标签 #AskAndroid 提出问题。我们的专家会全周关注并回答您的问题。
敬请关注明天的内容,届时我们将讨论如何使用基准配置文件和启动配置文件进行配置文件引导的优化,分享 Compose 渲染性能在过去几个版本中的提升情况,并分享后台工作的性能注意事项。
-
产品资讯从家中的全沉浸式体验,到外出时的快速浏览体验,基于 Android XR 进行开发,让您能够在各种空间应用场景中随时随地触达用户。在 Android 开发者峰会上,我们推出了 XREAL AURA,这是一个令人兴奋的里程碑。
Amy Zeppenfeld, Vinny DaSilva • 阅读用时:3 分钟 -
产品资讯在智能时代,用户不再只是在应用中点按,而是表达高级目标,并期望 Gemini 等 AI 智能体完成多步骤工作流,有时甚至会跨设备上的多个应用。
Jingyu Shi • 阅读用时:4 分钟 -
产品资讯在今天的 Android 开发者峰会上,我们宣布正式推出多项工具和 API,旨在消除开发过程中的日常障碍,让您能够更灵活地构建出色的 Android 应用。
Matthew McCullough • 阅读用时:4 分钟
每周通过电子邮件接收最新的 Android 开发洞见。