诊断稳定性问题

如果您遇到因不必要或过度重组而导致的性能问题,则应调试应用的稳定性。本指南概述了执行此调试的几种方法。

布局检查器

利用 Android Studio 中的布局检查器,您可以查看哪些可组合项在应用中执行了重组。其中会显示 Compose 执行重组或跳过组件的次数。

布局检查器中的重组和跳过次数

Compose 编译器报告

Compose 编译器可以输出其稳定性推断的结果,以供检查。利用输出的这些结果,您可以确定哪些可组合项可跳过,哪些不可跳过。以下小节总结了如何使用这些报告,但如需了解更详细的信息,请参阅技术文档。

设置

Compose 编译器报告并非默认启用。您可以通过编译器标记进行激活。确切的设置因项目而异,但对于使用 Compose 编译器 Gradle 插件的项目,您可以在每个模块的 build.gradle 文件中添加以下内容。

  android { ... }

  composeCompiler {
    reportsDestination = layout.buildDirectory.dir("compose_compiler")
    metricsDestination = layout.buildDirectory.dir("compose_compiler")
  }

现在,在构建项目时,系统会生成 Compose 编译器报告。

输出示例

reportsDestination 会输出三个文件。以下是 JetSnack 中的输出示例。

  • <modulename>-classes.txt:关于本模块中类稳定性的报告。示例。
  • <modulename>-composables.txt:关于模块中可组合项的可重启和可跳过程度的报告。示例。
  • <modulename>-composables.csv:可组合项报告的 CSV 版本,您可以将其导入电子表格或使用脚本进行处理。示例。

可组合项报告

composables.txt 文件中会详细说明给定模块的每个可组合函数的相关情况,包括其参数的稳定性以及它们是否可重启或可跳过。以下是 JetSnack 中的假设示例:

restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun SnackCollection(
  stable snackCollection: SnackCollection
  stable onSnackClick: Function1<Long, Unit>
  stable modifier: Modifier? = @static Companion
  stable index: Int = @static 0
  stable highlight: Boolean = @static true
)

在此示例中,SnackCollection 的每个参数都标记为 stable,因此 Compose 会使用结构相等性 (Object.equals()) 比较每个参数。虽然 stable 参数非常适合基元值和具有低成本 .equals() 检查的小型扁平数据类,但强制将包含集合的模型(例如封装 List<Snack> 的 SnackCollection)设为 stable 会导致 Compose 在每次重组时对每个项调用 .equals()。启用强力跳过模式后,最好保留包含集合 unstable 的模型,以便 Compose 改用实例相等性 (===) 来比较它们。

或者,考虑在停用强力跳过模式的情况下生成的另一个示例:

restartable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
  stable index: Int
  unstable snacks: List<Snack>
  stable onSnackClick: Function1<Long, Unit>
  stable modifier: Modifier? = @static Companion
)

停用强力跳过模式后(Kotlin 2.0.20 之前),HighlightedSnacks 可组合函数不可跳过,即使其参数均未发生变化,Compose 也绝不会在重组期间跳过它,因为 unstable 参数 snacks。

启用强力跳过模式(Kotlin 2.0.20 及更高版本中的默认模式)后,HighlightedSnacks 会被标记为 skippable,Compose 会使用实例相等性 (===) 比较 unstable 参数 snacks。因此,如果传递了相同的 List 引用,Compose 会跳过 HighlightedSnacks,并且仅在传递了新分配的 List 实例时才会重组它。

类报告

文件 classes.txt 中包含关于给定模块中的类的类似报告。以下代码段是针对类 Snack 的输出:

unstable class Snack {
  stable val id: Long
  stable val name: String
  stable val imageUrl: String
  stable val price: Long
  stable val tagline: String
  unstable val tags: Set<String>
  <runtime stability> = Unstable
}

以下是 Snack 的定义,供参考:

data class Snack(
    val id: Long,
    val name: String,
    val imageUrl: String,
    val price: Long,
    val tagline: String = "",
    val tags: Set<String> = emptySet()
)

Compose 编译器将 Snack 标记为了不稳定。这是因为 tags 参数的类型是 Set<String>。这是一个不可变类型,因为它不是 MutableSet。然而,Set、List 和 Map 等标准集合类最终都是接口。因此,底层实现可能仍然可变。

例如,您可以编写 val set: Set<String> = mutableSetOf("foo")。该变量是常量,其声明的类型不可变,但其实现仍然可变。Compose 编译器无法确定此类的不可变性,因为它只能看到声明的类型,因此将 tags 标记为了不稳定。