作为库的作者,您必须确保应用开发者能够轻松地将您的库纳入其应用中,同时保持高质量的最终用户体验。这意味着您的库必须与 Android 优化 (R8) 兼容,而无需开发者进行额外设置;或者,您需要说明该库可能不适合在 Android 上使用。至关重要的是,旨在用于 Android 的库不得阻止重要的应用优化,并且必须遵守其他优化要求。
本文档面向已发布库的开发者,但对于大型模块化应用中的内部库模块的开发者也可能很有用。
如果您是应用开发者,并想了解如何优化 Android 应用,请参阅启用应用优化。如需了解哪些库适合使用,请参阅明智地选择库。
了解保留规则类型
媒体库中可以包含两种不同的保留规则:
- 使用方 keep 规则必须指定用于保留库所反映内容的规则。如果库使用反射或 JNI 调用其代码或客户端应用定义的代码,则这些规则需要描述需要保留哪些代码。库应打包使用方 keep 规则,该规则的格式与应用 keep 规则相同。这些规则会捆绑到库制品(AAR 或 JAR)中,并在使用相应库时在 Android 应用优化期间自动使用。这些规则保存在
build.gradle.kts(或build.gradle)文件中通过consumerProguardFiles属性指定的文件中。如需了解详情,请参阅编写消费者保留规则。 - 库 build 保留规则会在构建库时应用。如果您决定在 build 时部分优化库,才需要这些标志。它们必须防止库的公共 API 被移除,否则库分发中将不包含公共 API,这意味着应用开发者无法使用该库。这些规则保存在
build.gradle.kts(或build.gradle)文件中通过proguardFiles属性指定的文件中。如需了解详情,请参阅优化 AAR 库 build。
优化要求和指南
库中的 R8 配置会对使用应用的最终二进制文件大小和性能产生全局影响。除了常规的保留规则最佳实践之外,库作者还必须遵守特定要求,并考虑其他指南。
遵守优化要求
库的低效是导致应用臃肿、内存浪费、启动缓慢和 ANR(应用无响应错误)的主要原因。库必须避免违反以下要求,以免显著降低应用质量和用户体验。
没有广泛的或软件包范围的保留规则:您的库不得包含保留库中或另一库中大部分代码的广泛保留规则。宽泛的保留规则可能在短期内解决崩溃问题,但会增加使用您的库的所有应用的体积。
请勿为库或其他被引用的库中的软件包添加软件包范围的保留规则(例如
-keep class com.mylibrary.** {*; })。此类规则会限制所有使用您的库的应用对这些软件包进行优化。无不当的全局规则:切勿使用
-dontobfuscate或-allowaccessmodification等全局选项。尽可能使用代码生成而非反射:尽可能使用代码生成 (codegen) 而非反射。代码生成和反射都是编程时避免样板代码的常用方法,但代码生成与 R8 等应用优化器更兼容。
借助代码生成,可以在构建过程中分析和修改代码。由于编译后不会进行任何重大修改,因此优化器知道最终需要哪些代码,以及哪些代码可以安全地移除。
通过反射,可以在运行时分析和操纵代码。由于代码在执行之前并未真正最终确定,因此优化器不知道哪些代码可以安全地移除。它可能会移除在运行时通过反射动态使用的代码,从而导致应用崩溃。
支持 R8 完整模式:启用 R8 完整模式后,您的库不应崩溃。R8 的完整模式是建议使用的 R8 模式,自 2023 年发布的 AGP 8.0 稳定版以来,该模式已成为默认模式。如果您的库在 R8 下崩溃,解决方案是确定特定的反射或 JNI 入口点并添加有针对性的规则,而不是保留整个软件包。
其他建议
除了优化要求之外,以下是一些额外的建议。
- 请勿在库的消费者保留规则文件中使用
-repackageclasses。不过,为了优化库 build,您可以在库的 build keep 规则文件中使用带有内部软件包名称(例如<your.library.package>.internal)的-repackageclasses。这可以提高未优化应用的库效率。不过,一般情况下没有必要这样做,因为应用也应进行优化。 - 在库的保留规则文件中声明库正常运行所需的任何属性,即使这些属性可能与
proguard-android-optimize.txt中定义的属性重叠。 - 如果您需要在库分发中包含以下属性,请在库的 build keep 规则文件中保留这些属性,而不是在库的 consumer keep 规则文件中保留这些属性:
AnnotationDefaultEnclosingMethodExceptionsInnerClassesRuntimeInvisibleAnnotationsRuntimeInvisibleParameterAnnotationsRuntimeInvisibleTypeAnnotationsRuntimeVisibleAnnotationsRuntimeVisibleParameterAnnotationsRuntimeVisibleTypeAnnotationsSignature
- 如果在运行时使用注释,库作者应在其使用方 keep 规则中保留
RuntimeVisibleAnnotations属性。 - 库作者不应在其消费者保留规则中使用以下全局选项:
-include-basedirectory-injars-outjars-libraryjars-repackageclasses-flattenpackagehierarchy-allowaccessmodification-renamesourcefileattribute-ignorewarnings-addconfigurationdebugging-printconfiguration-printmapping-printusage-printseeds-applymapping-obfuscationdictionary-classobfuscationdictionary-packageobfuscationdictionary
何时可以反思
如果您必须使用反射,则只能反射到以下任一位置:
- 特定目标类型(特定接口实现者或子类)
- 使用特定运行时注释的代码
以这种方式使用反射可限制运行时费用,并支持编写有针对性的消费者保留规则。
这种特定且有针对性的反射是您可以在 Android 框架(例如,在膨胀 activity、视图和可绘制对象时)和 AndroidX 库(例如,在构建 WorkManager
ListenableWorkers 或 RoomDatabases 时)中看到的模式。相比之下,Gson 的开放式反射不适合在 Android 应用中使用。
常见误解
一些常见的误解可能会导致您错误地配置 R8。这些情况包括:
对 R8 的优化功能理解有误:与普遍的理解相反,R8 的优化功能不仅限于混淆,还包括代码缩减和逻辑优化,并采用方法内联和类合并技术。如需了解详情,请参阅 R8 优化概览。
绕过混淆库的优化:一个常见错误是将某个库从优化中省略,因为该库在编译为 AAR(Android 归档)或 JAR(Java 归档)时已优化或混淆。库构建期间的优化有限,您的应用不应通过在保留规则中包含库来停用库的优化。如需了解详情,请参阅优化 AAR 库 build。
对
-keep选项的理解有误-keep规则会阻止 R8 运行任何优化传递。如需了解详情,请参阅选择合适的保留选项。
配置规则打包
为确保您的消费者保留规则得到正确应用,您必须根据库格式对其进行适当打包。
AAR 库
如需为 AAR 库添加消费者规则,请在 Android 库模块的 build 脚本中使用 consumerProguardFiles 选项。如需了解详情,请参阅我们的库模块创建指南。
Kotlin
android {
defaultConfig {
consumerProguardFiles("consumer-proguard-rules.pro")
}
...
}
Groovy
android {
defaultConfig {
consumerProguardFiles 'consumer-proguard-rules.pro'
}
...
}
JAR 库
如需将规则与以 JAR 形式提供的 Kotlin 或 Java 库捆绑在一起,请将规则文件放在最终 JAR 的 META-INF/proguard/ 目录中,并使用任意文件名。例如,如果您的代码位于 <libraryroot>/src/main/kotlin 中,请在 <libraryroot>/src/main/resources/META-INF/proguard/consumer-proguard-rules.pro 中放置一个消费者规则文件,这些规则将捆绑在输出 JAR 中的正确位置。
通过检查规则是否位于 META-INF/proguard 目录中,验证最终 JAR 是否正确捆绑了规则。
优化 AAR 库 build(高级)
一般来说,您无需直接优化库 build,因为库 build 时的可能优化非常有限。作为库开发者,您需要在优化库之前,推理出多个优化阶段,并了解库和应用 build 时的行为。
如果您仍想在构建时优化库,Android Gradle 插件支持此操作。
Kotlin
android {
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
configureEach {
consumerProguardFiles("consumer-rules.pro")
}
}
}
Groovy
android {
buildTypes {
release {
minifyEnabled true
proguardFiles
getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
}
configureEach {
consumerProguardFiles "consumer-rules.pro"
}
}
}
请注意,proguardFiles 的行为与 consumerProguardFiles 的行为截然不同:
proguardFiles在构建时使用,通常与getDefaultProguardFile("proguard-android-optimize.txt")一起使用,用于定义在库构建期间应保留库的哪些部分。至少是您的公共 API。- 相比之下,
consumerProguardFiles会打包到库中,以影响后续在构建使用您的库的应用期间发生的优化。
例如,如果您的库使用反射来构建内部类,您可能需要在 proguardFiles 和 consumerProguardFiles 中同时定义 keep 规则。
如果您在库的 build 中使用 -repackageclasses,请将类重新打包到库软件包内的子软件包中。例如,使用 -repackageclasses
'com.example.mylibrary.internal' 而不是 -repackageclasses 'internal'。
支持不同的 R8 版本(高级)
您可以自定义规则,以定位特定版本的 R8。这样一来,您的库就可以在采用较新 R8 版本的项目中以最佳方式运行,同时允许在采用较旧 R8 版本的项目中继续使用现有规则。
如需指定有针对性的 R8 规则,您需要将这些规则包含在 AAR 的 classes.jar 内的 META-INF/com.android.tools 目录中,或 JAR 的 META-INF/com.android.tools 目录中。
In an AAR library:
proguard.txt (legacy location, the file name must be "proguard.txt")
classes.jar
└── META-INF
└── com.android.tools (location of targeted R8 rules)
├── r8-from-<X>-upto-<Y>/<R8-rule-files>
└── ... (more directories with the same name format)
In a JAR library:
META-INF
├── proguard/<ProGuard-rule-files> (legacy location)
└── com.android.tools (location of targeted R8 rules)
├── r8-from-<X>-upto-<Y>/<R8-rule-files>
└── ... (more directories with the same name format)
在 META-INF/com.android.tools 目录中,可以有多个名称为 r8-from-<X>-upto-<Y> 形式的子目录,用于指明规则是为哪个 R8 版本编写的。每个子目录都可以包含一个或多个包含 R8 规则的文件,这些文件可以采用任何名称和扩展名。
请注意,-from-<X> 和 -upto-<Y> 部分是可选的,<Y> 版本是互斥的,版本范围通常是连续的,但也可以重叠。
例如,r8、r8-upto-8.0.0、r8-from-8.0.0-upto-8.2.0 和 r8-from-8.2.0 是表示一组目标 R8 规则的目录名称。r8 目录下的规则可供任何 R8 版本使用。r8-from-8.0.0-upto-8.2.0 目录下的规则可供 R8 使用,但仅限版本 8.0.0 到 8.2.0(不包括 8.2.0)。
Android Gradle 插件会使用该信息来选择当前 R8 版本可使用的所有规则。如果库未指定目标 R8 规则,Android Gradle 插件将从旧版位置(AAR 为 proguard.txt,JAR 为 META-INF/proguard/<ProGuard-rule-files>)选择规则。