强力跳过是 Compose 编译器中提供的一种模式。启用后,它会通过以下两种方式更改编译器的行为:
启用强力跳过模式
Kotlin 2.0.20 中默认启用强跳过模式。
如需在 2.0.20 之前的版本中为 Gradle 模块启用强跳过模式,请在 Gradle 配置的 composeCompiler 代码块中添加以下选项:
android { ... }
composeCompiler {
enableStrongSkippingMode = true
}
可组合项可跳过性
强跳过模式放宽了 Compose 编译器在跳过和可组合函数方面通常应用的一些稳定性规则。默认情况下,如果可组合函数的所有实参都具有稳定值,Compose 编译器会将该函数标记为可跳过。强力跳过模式改变了这一点。
启用强跳过模式后,所有可重启的可组合函数都将变为可跳过。无论这些参数是否不稳定,都适用此规则。 不可重启的可组合函数仍然不可跳过。
何时跳过
为了确定是否在重组期间跳过某个可组合项,Compose 会将每个参数的值与其先前的值进行比较。比较类型取决于形参的稳定性。
- 不稳定参数使用实例相等性 (
===) 进行比较 - 稳定参数使用对象相等性 (
Object.equals()) 进行比较
如果所有参数都满足这些要求,Compose 会在重组期间跳过该可组合项。
您可能希望某个可组合项停用强跳过模式。也就是说,您可能需要一个可重启但不可跳过的可组合项。在这种情况下,请使用 @NonSkippableComposable 注解。
@NonSkippableComposable
@Composable
fun MyNonSkippableComposable {}
将类注解为稳定
如果您希望使用对象相等性 (Object.equals()) 而不是实例相等性 (===) 来比较对象,请继续使用 @Stable 或 @Immutable 为给定类添加注解。
在添加 @Stable 或 @Immutable 之前,请注意以下权衡:
- Compose 模块中具有稳定属性的类已经稳定:在使用 Compose 编译器编译的模块中,具有稳定类型
val属性的类会自动推断为稳定,并使用.equals(),而无需@Immutable或@Stable。 - 仅为具有低成本
.equals()检查的类添加注解:当编译器无法推断稳定性(例如来自非 Compose 模块的类)并且经常分配具有相同值的新实例时,请添加@Stable或@Immutable,前提是该类的.equals()检查成本较低。 - 使用集合为模型添加注解时要谨慎:向包含集合的模型添加
@Stable或@Immutable(或将List替换为ImmutableList)会导致 Compose 在每次重组传递时将集合中的每个元素与.equals()进行比较。对于延迟布局(LazyColumn、LazyRow、LazyGrid)中使用的大型集合或模型,比较每个元素可能比使用实例相等性 (===) 或直接重组更慢。
Lambda 记忆化
强力跳过模式还支持对可组合项内的 lambda 进行更多记忆。启用强力跳过模式后,系统会自动记住可组合函数内的每个 lambda。
示例
为了在使用严格跳过模式时实现可组合项内 lambda 的记忆化,编译器会使用 remember 调用封装 lambda。它以 lambda 的捕获内容为键。
假设您有一个 lambda,如以下示例所示:
@Composable
fun MyComposable(unstableObject: Unstable, stableObject: Stable) {
val lambda = {
use(unstableObject)
use(stableObject)
}
}
启用强力跳过模式后,编译器会通过将 lambda 封装在 remember 调用中来对其进行记忆:
@Composable
fun MyComposable(unstableObject: Unstable, stableObject: Stable) {
val lambda = remember(unstableObject, stableObject) {
{
use(unstableObject)
use(stableObject)
}
}
}
这些键遵循与可组合函数相同的比较规则。运行时会使用实例相等性来比较不稳定键。它会使用对象相等性来比较稳定键。
记忆化和重组
这项优化可大幅增加运行时在重组期间跳过的可组合项数量。如果没有记忆功能,运行时在重组期间更有可能为任何接受 lambda 参数的可组合项分配新的 lambda。因此,新 lambda 的参数与上次组合不相等。这会导致重组。
避免记忆化
如果您有不想记忆的 lambda,请使用 @DontMemoize 注解。
val lambda = @DontMemoize {
...
}
APK 大小
编译时,可跳过的可组合项会比不可跳过的可组合项生成更多代码。启用强跳过模式后,编译器会将几乎所有可组合项标记为可跳过,并将所有 lambda 封装在 remember{...} 中。因此,启用强力跳过模式对应用的 APK 大小影响非常小。
在 Now In Android 中启用强跳过模式后,APK 大小增加了 4kB。大小差异主要取决于给定应用中之前存在的不可跳过可组合项的数量,但应该相对较小。