在现代 Android 开发中,交付小巧、快速且安全的应用程序是用户的基本期望。Android 构建系统实现此目的的主要工具是 R8 优化器,该编译器负责处理无用代码和资源移除以进行缩减、代码重命名或缩小,以及应用优化。
启用 R8 是准备发布应用的关键一步,但需要开发者以“保留规则”的形式提供指导。
阅读本文后,不妨前往 YouTube 观看“Performance Spotlight Week”视频,了解如何启用、调试和排查 R8 优化器问题。
为什么需要保留规则
之所以需要编写 Keep 规则,是因为存在一个核心冲突:R8 是一种静态分析工具,但 Android 应用通常依赖于动态执行模式,例如使用 JNI(Java 原生接口)在原生代码中进行反射或调用。
R8 通过分析直接调用来构建已使用代码的图。当以动态方式访问代码时,R8 的静态分析无法预测到这一点,因此会将该代码识别为未使用并将其移除,从而导致运行时崩溃。
保留规则是针对 R8 编译器的明确指令,用于声明:“此特定类、方法或字段是一个入口点,将在运行时动态访问。您必须保留它,即使您找不到对它的直接引用。”
如需详细了解 Keep 规则,请参阅官方指南。
在哪里编写保留规则
应用的自定义 Keep 规则以文本文件形式编写。按照惯例,此文件名为 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 组件提供默认的 Keep 规则,并启用 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 的优势,最简单的方法是编写过于宽泛的 Keep 规则。以下规则指示 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 添加 Keep 规则。这是不必要的。默认 proguard-android-optimize.txt 文件已包含相关规则,可让这些标准 Android 组件开箱即用。
此外,许多库都自带 Keep 规则。因此,您无需自行为这些情况编写规则。如果您使用的库中的 Keep 规则存在问题,最好与库作者联系,了解具体问题。
遵循规则最佳实践
既然您已经了解了不应采取的做法,下面我们来讨论一下最佳实践。
编写精细的保留规则
良好的保留规则应尽可能具体且范围尽可能小。它们应仅保留必要的内容,以便 R8 优化所有其他内容。
| 规则 | 质量 |
|---|---|
| 低:保留整个软件包及其子软件包 |
| 低:保留整个类,但可能仍然过宽 |
-keepclassmembers class com.example.MyClass {
private java.lang.String secretMessage;
public void onNativeEvent(java.lang.String);
} | 高:仅保留特定类的相关方法和属性 |
使用共同祖先
您可以编写一条以通用基类或接口为目标的规则,而不是为多个不同的数据模型分别编写 Keep 规则。以下规则会告知 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 选项 | 功能 |
-keep | 防止移除或重命名声明中提及的类和成员。 |
-keepclassmembers | 防止移除或重命名指定成员,但允许移除类本身,不过仅限于未以其他方式移除的类。 |
-keepclasseswithmembers | 一种组合:保留类及其成员,前提是所有指定的成员都存在。 |
如需详细了解 keep 选项,请参阅我们的“Keep 选项”文档。
允许使用修饰符进行优化
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 规则
如果您必须使用包含有问题 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 规则。对于许多应用,可以通过选择偏好使用代码生成而非反射的现代库来实现。借助代码生成功能,优化器可以更轻松地确定在运行时实际使用的代码以及可以移除的代码。此外,不使用任何动态反射意味着没有“隐藏”的入口点,因此不需要任何 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
在 build 期间,R8 将打印导致其保留相应类的确切引用链,以便您跟踪引用并调整规则。
如需查看完整指南,请参阅排查 R8 问题部分。
后续步骤
R8 是一款可提升 Android 应用性能的强大工具。它的有效性取决于对它作为静态分析引擎的运行方式的正确理解。
通过编写具体的会员级规则、利用祖先和注释,并仔细选择合适的保留选项,您可以准确保留所需内容。最先进的做法是选择基于代码生成的现代库,而不是基于反射的前代库,从而完全消除对规则的需求。
在关注“性能聚焦周”期间,请务必观看今天在 YouTube 上发布的聚焦周视频,并继续参与我们的 R8 挑战。如果您在启用或排查 R8 方面有任何问题,请使用 #optimizationEnabled。我们随时为您提供帮助。
现在是时候亲自体验这些福利了。
我们建议您今天为应用启用 R8 完整模式。
- 请参阅我们的开发者指南,了解如何开始使用:启用应用优化。
- 检查您是否仍在使用
proguard-android.txt,并将其替换为proguard-android-optimize.txt。 - 然后,衡量效果。不要只是感觉到差异,还要验证差异。通过调整 GitHub 上的 Macrobenchmark 示例应用中的代码来衡量性能提升,以测量启动时间前后。
我们相信,您应用的性能将得到显著提升。
届时,欢迎使用社交媒体标签 #AskAndroid 提出您的问题。在整个周内,我们的专家都会监控并回答您的问题。
敬请期待明天的内容,我们将讨论使用基准配置文件和启动配置文件的由配置文件引导的优化,分享 Compose 渲染性能在过去版本中的改进情况,并分享后台工作的性能注意事项。
-
产品新闻作为 Android 开发者,您在选择用于应用开发的智能体、大语言模型、工具和命令行界面 (CLI) 时有很多选择。我们的目标是帮助您构建美观优质的 Android 应用,无论您选择以何种方式构建。
Simona Milanovic • 阅读用时:4 分钟 -
产品新闻在 Google Play 上,我们一直在不断扩展订阅平台,以帮助您促进业务增长、适应新的业务模式,并满足用户的需求。
Sheenam Mittal • 阅读用时:4 分钟 -
产品新闻去年,Android Studio 开始支持任何 AI 模型。今天,我们将推出对您选择的编码智能体的支持,从而迈出下一步。
Matthew Warner • 阅读用时:3 分钟
每周通过电子邮件接收最新的 Android 开发洞见。