为了启用应用优化,您必须使用与 Android 优化兼容的库。如果某个库未配置为支持 Android 优化(例如,它使用 反射 但未捆绑相关的保留规则),则它可能不适合 Android 应用。本页面介绍了为什么某些库更适合应用优化,并提供了帮助您进行选择的一般性提示。
优先使用代码生成而非反射
通常,您应选择使用 代码生成 (codegen) 而非反射的库。借助代码生成,优化器可以更轻松地确定在运行时实际使用了哪些代码以及可以移除哪些代码。很难判断库是使用代码生成还是反射,但有一些迹象可以帮助您判断,请参阅提示以获取帮助。
如需详细了解代码生成与反射的对比,请参阅适用于库作者的优化。
选择库时的一般性提示
使用以下提示,帮助确保您的库与应用优化兼容。
检查优化问题
在考虑新库时,请查看该库的问题跟踪器和在线讨论,检查是否存在与代码压缩或配置应用优化相关的问题。如果存在此类问题,您应尝试寻找该库的替代品。请记住以下几点:
- AndroidX 库和 Hilt 等库与应用优化兼容性良好,因为它们使用代码生成而非反射。即使它们确实使用反射,也提供了最少的保留规则,仅保留所需的代码。
- 序列化库经常使用反射来避免在实例化或序列化对象时产生样板代码。与基于反射的方法(如用于 JSON 的 Gson)不同,请寻找使用代码生成来避免这些问题的库,例如,使用 Kotlin Serialization。
- 应尽可能避免使用包含包范围保留规则的库。包范围保留规则有助于解决错误,但宽泛的保留规则最终应进行细化,以仅保留所需的代码。如需了解详情,请参阅逐步采用优化。
添加新库后启用优化
添加新库后,请启用优化并检查是否存在错误。如果存在错误,请寻找该库的替代品或编写保留规则。如果库与优化不兼容,请向该库提交错误报告。
规则是累加的
请注意,保留规则是累加的。这意味着库依赖项中包含的某些规则无法移除,并且可能会影响应用其他部分的编译。例如,如果一个库包含一个禁用代码优化的规则,那么该规则将禁用您整个项目的优化。
检查反射的使用(高级)
您可以通过检查库的代码来判断它是否使用反射。如果库使用反射,请检查它是否提供了相关的保留规则。如果一个库执行以下操作,则它很可能正在使用反射:
- 使用
kotlin.reflect
或java.lang.reflect
软件包中的类或方法 - 使用函数
Class.forName
或classLoader.getClass
- 在运行时读取注解,例如,如果它使用
val value = myClass.getAnnotation()
或val value = myMethod.getAnnotation()
存储注解值,然后对value
执行某些操作 使用方法名作为字符串调用方法,例如
// Calls the private `processData` API with reflection myObject.javaClass.getMethod("processData", DataType::class.java) ?.invoke(myObject, data)
剔除无效的保留规则 (高级)
滤除不良保留规则(高级)
// If you're using AGP 8.4 and higher
buildTypes {
release {
optimization.keepRules {
it.ignoreFrom("com.somelibrary:somelibrary")
}
}
}
// If you're using AGP 7.3-8.3
buildTypes {
release {
optimization.keepRules {
it.ignoreExternalDependencies("com.somelibrary:somelibrary")
}
}
}
您应避免使用包含保留规则的库,这些规则保留了本应移除的代码。但如果您必须使用它们,可以按以下代码所示滤除这些规则:
案例研究:为何 Gson 会因优化而中断
- Gson 是一个序列化库,它经常会给应用优化带来问题,因为它大量使用了反射。以下代码段展示了 Gson 的典型用法,这种用法在运行时很容易导致崩溃。请注意,当您使用 Gson 获取 User 对象列表时,您既未调用构造函数,也未将工厂传递给
fromJson()
函数。未通过以下任一方式构建或使用应用定义的类,即表示库可能正在使用开放式反射: - 实现库或标准接口或类的应用类
class User(val name: String)
class UserList(val users: List<User>)
// This code runs in debug mode, but crashes when optimizations are enabled
Gson().fromJson("""[{"name":"myname"}]""", User::class.java).toString()
KSP 等代码生成插件
当 R8 分析此代码但未在任何位置看到 UserList
或 User
被实例化时,它可能会重命名字段或移除看似未使用的构造函数,从而导致您的应用崩溃。如果您以类似方式使用任何其他库,您应检查它们是否会干扰应用优化;如果会,则应避免使用它们。