作为库作者,您应确保应用开发者能够轻松将您的库集成到其应用中,同时保持高质量的最终用户体验。您应确保您的库无需额外设置即可与 Android 优化兼容,或者说明该库可能不适合在 Android 上使用。
本文档面向发布库的开发者,但对于大型模块化应用中的内部库模块开发者来说,也可能很有用。
如果您是应用开发者,并且想要了解如何优化 Android 应用,请参阅启用应用优化。如需了解哪些库适合使用,请参阅明智地选择库。
使用 codegen 而非反射
尽可能使用代码生成 (codegen) 而非反射。在编程时,代码生成和反射都是避免样板代码的常用方法,但代码生成与 R8 等应用优化器更兼容:
- 借助 codegen,系统会在构建过程中分析和修改代码。由于编译后不会进行任何重大修改,因此优化器知道最终需要哪些代码,哪些代码可以安全移除。
- 借助反射,系统会在运行时分析和操控代码。由于代码在执行之前不会真正完成,因此优化器不知道哪些代码可以安全移除。它可能会移除在运行时通过反射动态使用的代码,从而导致用户应用崩溃。
许多现代库使用 codegen 而非反射。如需了解 Room、Dagger2 等众多库使用的通用入口点,请参阅 KSP。
何时可以进行反思
如果您必须使用反射,则应仅反射到以下任一位置:
- 特定的目标类型(特定的接口实现者或子类)
- 使用特定运行时注解的代码
以这种方式使用反射可以限制运行时开销,并支持编写有针对性的使用方保留规则。
这种特定且有针对性的反射形式是一种模式,您可以在 Android 框架(例如在膨胀 activity、视图和可绘制对象时)和 AndroidX 库(例如在构建 WorkManager ListenableWorker 或 RoomDatabase 时)中看到这种模式。相比之下,Gson 的开放式反射不适合在 Android 应用中使用。
编写使用方保留规则
库应打包“使用方”保留规则,这些规则的格式与应用保留规则相同。这些规则会捆绑到库工件 (AAR 或 JAR) 中,并在 Android 应用优化期间使用该库时自动被使用。
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 时可进行的优化非常有限。只有在应用构建期间,当库作为应用的一部分包含时,R8 才能知道库的所有方法的使用方式以及传递了哪些参数。作为库开发者,您需要在优化库之前,考虑优化的多个阶段,并在库和应用构建时保留行为。
如果您仍希望在构建时优化库,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>
)选择规则。