如需启用应用优化,您必须使用与 Android 优化兼容的库。如果某个库未针对 Android 优化进行配置(例如,如果它使用 reflection 但未捆绑关联的 keep 规则),则可能不适合 Android 应用。本页将说明为什么某些库更适合应用优化,并提供一些常规提示来帮助您进行选择。
首选 codegen 而非反射
通常,您应选择使用代码生成 (codegen) 而非反射的库。借助 codegen,优化器可以更轻松地确定运行时实际使用的代码以及可以移除的代码。很难确定库是使用 codegen 还是反射,但有一些迹象可以帮助您判断 - 请参阅提示。
如需详细了解 codegen 与反射,请参阅面向库作者的优化。
选择库的一般提示
请参考以下提示,确保您的库与应用优化兼容。
检查优化问题
在考虑使用新库时,请查看该库的问题跟踪器和在线讨论,检查是否存在与缩减大小或配置应用优化相关的问题。如果有,您应尝试寻找该库的替代方案。请注意以下几点:
- AndroidX 库和 Hilt 等库非常适合应用优化,因为它们使用 codegen 而非反射。当它们使用反射时,会提供最少的保留规则,以仅保留所需的代码。
- 序列化库在实例化或序列化对象时,经常使用反射来避免使用样板代码。请寻找使用 codegen 来避免这些问题的库,而不是基于反射的方法(例如,使用 Gson 处理 JSON)。例如,改用 Kotlin 序列化。
- 应尽可能避免使用包含软件包级保留规则的库。软件包级保留规则有助于解决错误,但最终应优化宽泛的保留规则,以保留仅需的代码。如需了解详情,请参阅逐步采用优化。
在添加新库后启用优化
添加新库后,请启用优化,然后检查是否有错误。如果出现错误,请查找该库的替代方案或编写保留规则。如果某个库不兼容优化,请针对该库提交 bug。
规则是累加的
请注意,保留规则可累加。也就是说,库依赖项包含的某些规则无法移除,并且可能会影响应用其他部分的编译。例如,如果某个库包含停用代码优化的规则,该规则将针对整个项目停用优化。
检查是否使用了反射(高级)
您或许可以通过检查库的代码来确定该库是否使用了反射。如果库使用反射,请检查它是否提供了关联的保留规则。如果库执行以下操作,则可能使用反射:
- 使用
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)
滤除不良的保留规则(高级)
您应避免使用包含会保留应真正移除的代码的 keep 规则的库。不过,如果您必须使用这些规则,可以滤除这些规则,如以下代码所示:
// 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()
函数。如果在构建或使用应用定义的类时不满足以下任一条件,则表明该库可能正在使用开放式反射:
- 实现库、标准接口或类的应用类
- 代码生成插件,例如 KSP
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()
当 R8 分析此代码并未在任何位置看到实例化的 UserList
或 User
时,它可能会重命名字段或移除似乎未使用的构造函数,从而导致应用崩溃。如果您以类似方式使用任何其他库,则应检查它们是否会干扰应用优化;如果会,请避免使用它们。
请注意,Room 和 Hilt 都构建应用定义的类型,但使用 codegen 来避免需要反射。