Jetpack Compose 优化之调试重组和性能监控

527 阅读5分钟

Compose 是一种相对年轻的技术, 用于编写声明式UI. 许多开发人员甚至没有意识到, 他们在如此关键的部分编写了次优代码, 以致后来导致了意想不到的性能低下和指标下降.

这是我<Jetpack Compose优化>系列文章的第三篇, 其主题是有关编码过程中的一些小建议, 以及关于重组的调试和性能的监控. 其余2篇为:

Jetpack Compose 优化之可组合函数和Stable类型优化

Jetpack Compose 优化之Skip, 长计算和布局优化

Jetpack Compose 优化之调试重组和性能监控

其他建议

避免反向写入

这个错误对于初学者来说比较典型. 我们在读取后立即调用状态变化, 这会导致重新组合.

@Composable
fun BadComposable() {
    var count by remember { mutableStateOf(0) }

    // Causes recomposition on click
    Button(onClick = { count++ }) {
        Text("Recompose")
    }

    Text("$count")
    count++ 
    // Backwards write, writing to state after it has been read
}

MovableContentOf

当我们想将元素移动到不同的位置, 而不需要重新调用重新组合或丢失 memoized 状态时,movableContentOf就非常有用:

val myItems = remember {  
    movableContentOf {  
        MyItem(1)  
        MyItem(2)
    }  
}

if (isHorizontal) {  
    Row {  
        myItems()
    }  
} else {  
    Column {  
        myItems()  
    }  
}

更多信息请参阅 Jorge Castillo 撰写的文章.

一个有趣的事实是: key()构造与movableContentOf()在逻辑上类似, 因为这两种方法都使用了可移动组, 这样就可以在不重新组合的情况下移动Compose代码.

staticCompositionLocalOf 和 compositionLocalOf

当大量可组合函数使用 Composition Local 且其值不太可能改变时, 通常需要使用staticCompositionLocalOf. 实现示例: LocalContext,LocalLifecycleOwner,LocalDensity,LocalFocusManager等.

由于必须跟踪所有读取当前值的可组合函数, compositionLocalOf会在最初构建组合树时产生开销. 如果值会经常变化, compositionLocalOf可能是更好的选择. 实现示例 LocalConfiguration,LocalAlpha等.

@ReadOnlyComposable

如果一个可组合函数只执行读操作, 你可以用@ReadOnlyComposable注解来标记它. 这样, 它就不会生成组. 这将带来微小的性能提升. 主要用例是只需要@Composable注解来读取CompositionLocal(例如从主题中读取颜色)而不需要调用其他可组合函数的函数. 更多信息请访问 Android Developers.

更少使用ComposeView

这很简单: 与 View 桥接时使用的ComposeView越少, Compose 的运行速度就越快. 此外, Compose代码在应用启动时出现得越早, 后续代码就会运行得越好, 因为Compose将有时间"热身".

使用最新版本的 Compose

Compose开发人员几乎在每个版本中都会改进性能, 因此不要忘记更新.

基线配置文件

基线配置文件在Android Developers 上有详细记录. 一般来说, Google 已经通过 Cloud Profiles 为我们完成了这类工作. 如果我们想在较旧的 Android 版本(7-8.1)和较新的 Android 版本(9+)的新版本发布之初改进我们的指标, 基线配置文件会有所帮助.

调试和性能监控

优化固然是好事, 但最好还是对所做更改进行测试, 以确保问题确实得到了解决, 而不是增加了新的问题.

你应该在release模式和 R8 模式下检查性能. Debug模式在开发过程中好处多多, 它会减慢和扭曲最终的应用代码. R8 编译器还能显著优化代码.

此外, 这篇文章Android Developers提供的视频 都对 Compose 调试进行了详细介绍.

稳定性和可跳过性测试

要检查项目中类型和可组合函数的稳定性, 需要运行度量指标生成. 具体方法如下可组合指标解释性Compose编译器度量指标.

指标文件的路径为: your\_module/build/compose\_metrics. 其中, 有两个文件很重要:

  • \-classes.txt 用于类型度量;
unstable class WidgetUiState {
  unstable val widgets: List<Widget>
  stable val showLoader: Boolean
  <runtime stability> = Unstable
}
  • \-composables.txt.csv用于函数度量;
restartable scheme("[androidx.compose.ui.UiComposable]") fun MyWidgets(
  unstable widgets: List<Widget>
  stable modifier: Modifier? = @static Companion
)
  • \-module.json用于模块统计.

还有一个用于在 HTML 中显示指标的库: Compose Compiler Report to HTML.

调试重组

为了避免重复, 我建议你观看关于调试重组的视频. 重点是打开重组和跳过计数. 然后, 在屏幕上进行正常操作. 然后看看哪些地方出现了本可以避免的不必要的重组, 或者看看哪些地方出现了重组, 尽管数据并未发生变化, 而且可能出现跳转.

调试重组. Source

你还可以使用Rebugger来调试重组. 这个库允许你跟踪给定参数的变化, 并输出重新组合的原因.

Rebugger. Source

Android Studio Hedgehog will add additional information to debuggers to view Compose state.

Android Studio Hedgehog将为调试器添加额外信息, 以查看组合状态.

调试器中的Compose状态信息

组合跟踪

使用组合跟踪可以深入分析 UI 元素的问题. 这在文章"组合跟踪"本视频中有一些描述.

组合跟踪.

基准测试

使用基准测试优化后的页面(例如, 测量帧渲染持续时间的列表滚动基准).

总结一下

在本文中, 我们介绍了团队在实践中遇到和使用的所有优化方法. 有些技巧可以转换成你团队的代码风格并自由使用. 其他技巧只有在元素经常变化时才有用. 每次优化后都要检查性能, 以避免性能下降. 此外, 也没有必要事先对代码进行过度优化, 否则会导致代码变得难以阅读.