Compose 中的稳定性

Compose 将类型视为稳定或不稳定。如果类型不可变,或者 Compose 是否可以知道其值在重组之间是否发生了更改,则该类型就是稳定的类型。如果 Compose 无法确定其值在重组之间是否发生更改,则该类型不稳定。

Compose 根据可组合项参数的稳定性来确定是否可以在重组期间跳过可组合项:

  • 稳定的参数:如果可组合项具有未更改的稳定参数,Compose 会跳过它。
  • 参数不稳定:如果可组合项的参数不稳定,Compose 在重组组件的父级时始终会重组该可组合项。

如果您的应用包含许多不必要且不稳定的组件,Compose 会始终重组这些组件,您可能会遇到性能问题和其他问题。

本文档详细介绍了如何提高应用的稳定性以提升性能和整体用户体验。

不可变对象

以下代码段演示了稳定性和重组背后的一般原则。

Contact 类是一个不可变的数据类。这是因为它的所有参数都是使用 val 关键字定义的基元。创建 Contact 的实例后,便无法更改对象的属性值。如果您尝试这样做,则需要创建一个新对象。

data class Contact(val name: String, val number: String)

ContactRow 可组合项有一个类型为 Contact 的参数。

@Composable
fun ContactRow(contact: Contact, modifier: Modifier = Modifier) {
   var selected by remember { mutableStateOf(false) }

   Row(modifier) {
      ContactDetails(contact)
      ToggleButton(selected, onToggled = { selected = !selected })
   }
}

考虑当用户点击切换按钮且 selected 状态发生变化时会发生什么情况:

  1. Compose 会评估是否应重组 ContactRow 内的代码。
  2. 它看到 ContactDetails 的唯一参数是 Contact 类型。
  3. 由于 Contact 是不可变的数据类,因此 Compose 可确保 ContactDetails 的任何参数都未更改。
  4. 因此,Compose 会跳过 ContactDetails,并且不会重组它。
  5. 另一方面,ToggleButton 的参数已更改,Compose 会重组该组件。

可变对象

虽然前面的示例使用的是不可变对象,但也可以创建可变对象。请参考以下代码段:

data class Contact(var name: String, var number: String)

由于 Contact 的每个参数现在都是 var,因此该类不再不可变。如果其属性发生更改,Compose 将不会意识到。这是因为 Compose 仅跟踪对 Compose 状态对象的更改。

Compose 认为此类不稳定。Compose 不会跳过不稳定类的重组。因此,如果以这种方式定义 Contact,则上一个示例中的 ContactRow 会在 selected 发生变化时重组。

Compose 中的实现

考虑 Compose 如何确切确定要在重组期间跳过哪些函数可能会有帮助(但并不重要)。

Compose 编译器在您的代码上运行时,会使用多个标记中的一个来标记每个函数和类型。这些标记反映了 Compose 在重组期间如何处理函数或类型。

函数

Compose 可以将函数标记为 skippablerestartable。请注意,它可以将函数标记为其中一个、两个或者两个也不标记:

  • 可跳过:如果编译器将可组合项标记为可跳过,则 Compose 可以在重组期间跳过它,前提是它的所有参数都与之前的值都相同。
  • 可重启:可重启的可组合项充当可开始重组的“作用域”。换言之,该函数可以作为入口点,在状态更改后,Compose 可以开始重新执行代码以进行重组。

类型

Compose 会将类型标记为不可变或稳定。每种类型都有其中一种:

  • 不可变:如果某种类型的属性值无法更改,并且所有方法都是引用透明的,则 Compose 会将该类型标记为不可变。
    • 请注意,所有基元类型均标记为不可变。其中包括 StringIntFloat
  • 稳定:表示其属性在构建后可以更改的类型。如果这些属性在运行时发生变化,Compose 会注意到这些更改。

调试稳定性

如果应用要重组参数未更改的可组合项,请先检查其定义,看看是否存在明显可变的参数。如果您传入具有 var 属性的类型或使用已知的不稳定类型的 val 属性,Compose 始终会重组组件。

如需详细了解如何在 Compose 中诊断与稳定性有关的复杂问题,请参阅调试稳定性指南。

解决稳定性问题

如需了解如何提高 Compose 实现的稳定性,请参阅修复稳定性问题指南。

总结

总体而言,您应注意以下几点:

  • 参数:Compose 确定可组合项中每个参数的稳定性,以确定在重组期间应跳过哪些可组合项。
  • 立即修复:如果您发现可组合项未被跳过并且会导致性能问题,则应先检查导致不稳定性的明显原因,例如 var 参数。
  • 编译器报告:您可以使用编译器报告来确定系统针对类推断出的稳定性。
  • 集合:Compose 始终将集合类视为不稳定,例如 List, SetMap。这是因为无法保证它们是不可变的。您可以改用 Kotlinx 不可变集合,或为类添加 @Immutable@Stable 注解。
  • 其他模块:对于 Compose 编译器不在其中运行的模块,Compose 始终认为其所在模块不稳定。根据需要将类封装在界面模型类中。

深入阅读