在现代 Android 开发中,发布小巧、快速且安全的应用是用户的基本期望。Android 构建系统实现此目标的主要工具是 R8 优化器,该编译器负责处理无用代码和资源移除,以实现缩减、代码重命名或缩减,以及应用优化。
启用 R8 是准备发布应用的关键步骤,但它要求开发者以“保留规则”的形式提供指导。
阅读本文后,请在 YouTube 上观看“Performance Spotlight Week”视频,了解如何启用、调试 R8 优化器并排查相关问题。
为何需要保留规则
之所以需要编写保留规则,是因为存在一个核心冲突:R8 是一种静态分析工具,但 Android 应用通常依赖于动态执行模式,例如使用 JNI(Java 原生接口)在原生代码中进行反射或调用。
R8 通过分析直接调用来构建 已用 代码图。当以动态方式访问代码时,R8 的静态分析无法预测这一点,因此它会将该代码标识为 未使用并将其移除,从而导致运行时崩溃。
保留规则 是对 R8 编译器的明确指令,它声明:“此特定类、方法或字段是一个入口点,将在运行时以动态方式访问。您必须保留它,即使您找不到对它的直接引用也是如此。”
如需详细了解保留规则,请参阅官方指南。
在哪里编写保留规则
应用的自定义保留规则是在文本文件中编写的。按照惯例,此文件名为 proguard-rules.pro,位于应用或库模块的根目录中。然后,在模块的 build.gradle.kts 文件的 release 构建类型中指定此文件。
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 仅提供保留规则,但不会 启用 R8 的优化功能。
由于这是一个严重的性能问题,因此我们开始警告开发者不要使用错误的文件,从 Android Studio Narwhal 3 功能更新开始。从 Android Gradle 插件 9.0 版开始,我们将不再支持过时的 proguard-android.txt 文件。因此,请务必升级到优化版本。
如何编写保留规则
保留规则由三个主要部分组成:
- 一个选项 ,例如
-keep或-keepclassmembers - 可选修饰符 ,例如
allowshrinking - 类规范 ,用于定义要匹配的代码
如需了解完整的语法和示例,请参阅添加保留规则指南。
保留规则反模式
了解最佳实践很重要,但了解反模式也很重要。这些反模式通常源于误解或问题排查快捷方式,可能会对生产构建的性能造成灾难性影响。
全局选项
这些标志是全局开关,绝不 应在发布 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 组件开箱即用的相关规则。
此外,许多库都自带保留规则。因此,您不应为这些库编写自己的规则。如果您使用的库中的保留规则存在问题,最好与库作者联系,了解问题所在。
保留规则最佳实践
现在您已经了解了不该做什么,接下来我们来谈谈最佳实践。
编写狭窄的保留规则
良好的保留规则应尽可能狭窄而具体 。它们应仅保留必要的内容,让 R8 优化其他所有内容。
| 规则 | 质量 |
|---|---|
| 低: 保留整个软件包及其子软件包 |
| 低: 保留整个类,这可能仍然过于宽泛 |
| 高: 仅保留特定类中的相关方法和属性 |
使用通用祖先
与其为多个不同的数据模型编写单独的保留规则,不如编写一条以通用基类或接口为目标的规则。以下规则指示 R8 保留实现此接口的任何类的任何成员,并且具有高度可扩缩性。
# Keep all fields of any class that implements SerializableModel -keepclassmembers class * implements com.example.models.SerializableModel { <fields>; }
使用注解以多个类为目标
创建一个自定义注解(例如 @Serialize),并使用它来“标记”需要保留其字段的类。这是另一种简洁、声明式且高度可扩缩的模式。您还可以为正在使用的框架中已有的注解创建保留规则。
# 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
添加全局选项以进行额外优化
除了保留规则之外,您还可以向 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")
}
...
}对于库消费者
过滤掉有问题的保留规则
如果您必须使用包含有问题的保留规则的库,则可以从 AGP 9.0 开始在 build.gradle.kts 文件中过滤掉这些规则。这会指示 R8 忽略来自特定依赖项的规则。
release {
optimization.keepRules {
// Ignore all consumer rules from this specific library
it.ignoreFrom("com.somelibrary:somelibrary")
}
}最好的保留规则是不保留规则
最终的 R8 配置策略是完全消除编写保留规则的需求 。对于许多应用来说,可以通过选择现代库来实现这一点,这些库倾向于使用代码生成 而不是反射。借助代码生成,优化器可以更轻松地确定在运行时实际使用的代码以及可以移除的代码。此外,不使用任何动态反射意味着没有“隐藏”的入口点,因此不需要保留规则。 选择新库时,请始终首选使用代码生成而不是反射的解决方案。
如需详细了解如何选择库,请参阅明智地选择库。
调试 R8 配置并排查相关问题
如果 R8 移除了它应该保留的代码,或者您的 APK 比预期大,请使用以下工具诊断问题。
查找重复的全局保留规则
由于 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 应用性能。其有效性取决于对 R8 作为静态分析引擎的运行方式的正确理解。
通过编写特定的成员级规则、利用祖先和注解,以及仔细选择正确的保留选项,您可以准确保留必要的内容。最先进的做法是完全消除对规则的需求,方法是选择基于代码生成的现代库,而不是基于反射的旧版库。
在观看“Performance Spotlight Week”视频时,请务必在 YouTube 上观看今天的 Spotlight Week 视频,并继续参与我们的 R8 挑战。如果您有关于启用 R8 或排查 R8 相关问题的任何疑问,请使用 #optimizationEnabled。我们会随时为您提供帮助。
现在是时候亲身体验这些优势了。
我们邀请您立即为应用启用 R8 完整模式 today。
- 请按照我们的开发者指南开始操作:启用应用优化。
- 检查您是否仍在使用
proguard-android.txt,并将其替换为proguard-android-optimize.txt。 - 然后,衡量所产生的影响 。不要只是 感受差异,而是要 验证差异。通过调整 GitHub 上的 Macrobenchmark 示例应用 中的代码来衡量性能提升,以衡量启动时间前后。
我们相信,您会看到应用的性能有显著提升。
同时,请使用社交媒体标签 #AskAndroid 提出您的问题。在整个一周内,我们的专家都会监控并回答您的问题。
请继续关注明天,我们将讨论使用基准配置文件和启动配置文件进行配置文件引导的优化,分享 Compose 呈现性能在过去版本中的改进情况,并分享后台工作的性能注意事项。
继续阅读
-
产品动态
每位开发者的 AI 工作流程和需求都是独一无二的,因此能够选择 AI 如何帮助您进行开发非常重要。1 月,我们推出了选择任何本地或远程 AI 模型来为 Android Studio 中的 AI 功能提供支持的功能
Matthew Warner • 2 分钟阅读时间
-
产品动态
Android Studio Panda 3 现在已是稳定版,可在生产环境中使用。此版本可让您更好地控制和自定义 AI 支持的工作流程,从而比以往更轻松地构建高质量的 Android 应用。
Matt Dyor • 3 分钟阅读时间
-
产品动态
在 Google,我们致力于将功能最强大的 AI 模型直接引入您口袋中的 Android 设备。今天,我们非常高兴地宣布推出最新的先进开放模型:Gemma 4。
Caren Chang, David Chou • 3 分钟阅读时间
随时了解最新动态
每周通过电子邮件接收最新的 Android 开发洞见 每周。