【问题标题】:Jetpack Compose Smart RecompositionJetpack Compose 智能重组
【发布时间】:2021-12-07 09:12:49
【问题描述】:

我正在做实验以理解重组和智能重组并制作了一个样本

对不起颜色,它们是用 Random.nextIn() 生成的,可以直观地观察重组,设置颜色对重组没有影响,也尝试过不改变颜色。

gif中的内容由三部分组成

样本1

@Composable
private fun Sample1() {

    Column(
        modifier = Modifier
            .background(getRandomColor())
            .fillMaxWidth()
            .padding(4.dp)
    ) {
        var counter by remember { mutableStateOf(0) }


        Text("Sample1", color = getRandomColor())

        Button(
            modifier = Modifier
                .fillMaxWidth()
                .padding(vertical = 4.dp),
            colors = ButtonDefaults.buttonColors(backgroundColor = getRandomColor()),
            onClick = {
                counter++
            }) {
            Text("Counter: $counter", color = getRandomColor())
        }
    }
}

我在这里没有问题,因为智能合成按预期工作,顶部的Text 没有读取counter 中的更改,因此仅在Button 内的Text 发生重组。

样本2

@Composable
private fun Sample2() {
    Column(
        modifier = Modifier.background(getRandomColor())
    ) {

        var update1 by remember { mutableStateOf(0) }
        var update2 by remember { mutableStateOf(0) }

        println("ROOT")
        Text("Sample2", color = getRandomColor())

        Button(
            modifier = Modifier
                .padding(start = 8.dp, end = 8.dp, top = 4.dp)
                .fillMaxWidth(),
            colors = ButtonDefaults.buttonColors(backgroundColor = getRandomColor()),
            onClick = {
                update1++
            },
            shape = RoundedCornerShape(5.dp)
        ) {

            println("???? Button1️")

            Text(
                text = "Update1: $update1",
                textAlign = TextAlign.Center,
                color = getRandomColor()
            )

        }

        Button(
            modifier = Modifier
                .padding(start = 8.dp, end = 8.dp, top = 2.dp)
                .fillMaxWidth(),
            colors = ButtonDefaults.buttonColors(backgroundColor = getRandomColor()),
            onClick = { update2++ },
            shape = RoundedCornerShape(5.dp)
        ) {
            println("???? Button 2️")

            Text(
                text = "Update2: $update2",
                textAlign = TextAlign.Center,
                color = getRandomColor()
            )
        }

        Column(
            modifier = Modifier.background(getRandomColor())
        ) {

            println("???? Inner Column")
            var update3 by remember { mutableStateOf(0) }

            Button(
                modifier = Modifier
                    .padding(start = 8.dp, end = 8.dp, top = 2.dp)
                    .fillMaxWidth(),
                colors = ButtonDefaults.buttonColors(backgroundColor = getRandomColor()),
                onClick = { update3++ },
                shape = RoundedCornerShape(5.dp)
            ) {

                println("✅ Button 3️")
                Text(
                    text = "Update2: $update2, Update3: $update3",
                    textAlign = TextAlign.Center,
                    color = getRandomColor()
                )

            }
        }

        Column() {
            println("☕️ Bottom Column")
            Text(
                text = "Sample2",
                textAlign = TextAlign.Center,
                color = getRandomColor()
            )
        }

    }
}

它也可以按预期工作,每个 mutableState 仅更新它们被观察到的范围。当这些 mutableState 中的任何一个被更新时,只有观察到 update2update3Text 会发生变化。

样本3

@Composable
private fun Sample3() {
    Column(
        modifier = Modifier.background(getRandomColor())
    ) {


        var update1 by remember { mutableStateOf(0) }
        var update2 by remember { mutableStateOf(0) }


        println("ROOT")
        Text("Sample3", color = getRandomColor())

        Button(
            modifier = Modifier
                .padding(start = 8.dp, end = 8.dp, top = 4.dp)
                .fillMaxWidth(),
            colors = ButtonDefaults.buttonColors(backgroundColor = getRandomColor()),
            onClick = {
                update1++
            },
            shape = RoundedCornerShape(5.dp)
        ) {

            println("???? Button1️")

            Text(
                text = "Update1: $update1",
                textAlign = TextAlign.Center,
                color = getRandomColor()
            )

        }

        Button(
            modifier = Modifier
                .padding(start = 8.dp, end = 8.dp, top = 2.dp)
                .fillMaxWidth(),
            colors = ButtonDefaults.buttonColors(backgroundColor = getRandomColor()),
            onClick = { update2++ },
            shape = RoundedCornerShape(5.dp)
        ) {
            println("???? Button 2️")

            Text(
                text = "Update2: $update2",
                textAlign = TextAlign.Center,
                color = getRandomColor()
            )
        }

        Column {

            println("???? Inner Column")
            var update3 by remember { mutableStateOf(0) }

            Button(
                modifier = Modifier
                    .padding(start = 8.dp, end = 8.dp, top = 2.dp)
                    .fillMaxWidth(),
                colors = ButtonDefaults.buttonColors(backgroundColor = getRandomColor()),
                onClick = { update3++ },
                shape = RoundedCornerShape(5.dp)
            ) {

                println("✅ Button 3️")
                Text(
                    text = "Update2: $update2, Update3: $update3",
                    textAlign = TextAlign.Center,
                    color = getRandomColor()
                )

            }
        }
       // ???????? Reading update1 causes entire composable to recompose
        Column(
            modifier = Modifier.background(getRandomColor())
        ) {
            println("☕️ Bottom Column")
            Text(
                text = "Update1: $update1",
                textAlign = TextAlign.Center,
                color = getRandomColor()
            )
        }
    }
}

Sample2Sample3 之间的唯一区别是底部的 Text 正在读取 update1 mutableState ,这会导致整个组合被重组。正如您在 gif 中看到的,更改 update1 会重构或更改 Sample3 的整个颜色模式。

重组整个可组合的原因是什么?

        Column(
            modifier = Modifier.background(getRandomColor())
        ) {
            println("☕️ Bottom Column")
            Text(
                text = "Update1: $update1",
                textAlign = TextAlign.Center,
                color = getRandomColor()
            )
        }
    }

【问题讨论】:

  • 请问您如何测试在一段代码中发生的重组?是否有任何工具或只有日志?
  • 在我发布的答案的文章中,有一个带有SideEffect 的日志代码,您可以计算重组次数。您可以使用 assertEquals 检查测试中的数字是否符合预期。

标签: android kotlin android-jetpack-compose


【解决方案1】:

让智能重组范围发挥关键作用。您可以查看 Vinay Gaba 的 What is “donut-hole skipping” in Jetpack Compose? 文章。

范围基本上是指传递给Composables 的未内联的 lambda,

Leland Richardson 在tweet 中解释为

“甜甜圈跳洞”的部分是一个新的 lambda 被传递给可组合(即按钮)可以在没有的情况下重新组合 重新编译它的其余部分。 lambda 被重组的事实 范围是您能够做到这一点所必需的,但不是 足够了

换句话说,可组合的 lambda 是“特殊的”:)

我们想这样做很长时间,但认为它也是 直到@chuckjaz 意识到如果 lambdas 是状态对象,调用是读取,那么这是 正是结果

创建了RandomColorColumn,它采用其他Composables 及其范围content: @Composable () -> Unit

@Composable
fun RandomColorColumn(content: @Composable () -> Unit) {

    Column(
        modifier = Modifier
            .padding(4.dp)
            .shadow(1.dp, shape = CutCornerShape(topEnd = 8.dp))
            .background(getRandomColor())
            .padding(4.dp)
    ) {
        content()
    }
}

并替换

 Column(
        modifier = Modifier.background(getRandomColor())
    ) {
        println("☕️ Bottom Column")
        Text(
            text = "Update1: $update1",
            textAlign = TextAlign.Center,
            color = getRandomColor()
        )
    }
}

    RandomColorColumn() {

        println("☕️ Bottom Column")
        /*
            ?? Observing update(mutableState) does NOT causes entire composable to recompose
         */
        Text(
            text = "? Update1: $update1",
            textAlign = TextAlign.Center,
            color = getRandomColor()
        )
    }
}

只有这个范围会按预期更新,并且我们有智能重组。

是什么导致Column 内部的Text 或任何可组合对象没有范围,因此当mutableState 值更改时被重构的是Column 在函数签名中具有inline 关键字。

@Composable
inline fun Column(
    modifier: Modifier = Modifier,
    verticalArrangement: Arrangement.Vertical = Arrangement.Top,
    horizontalAlignment: Alignment.Horizontal = Alignment.Start,
    content: @Composable ColumnScope.() -> Unit
) {
    val measurePolicy = columnMeasurePolicy(verticalArrangement, horizontalAlignment)
    Layout(
        content = { ColumnScopeInstance.content() },
        measurePolicy = measurePolicy,
        modifier = modifier
    )
}

如果您将 inline 添加到 RandomColorColumn 函数签名,您会看到它会导致 整个 Composable 重新组合

Compose 使用 call sites 定义为

调用站点是可组合项所在的源代码位置 叫。这会影响其在作曲中的位置,因此, 用户界面树。

如果在重组期间,一个可组合项调用不同的可组合项 与之前的组合相比,Compose 将识别 哪些可组合物被调用或没有被调用以及对于可组合物 在两个组合中都调用了,Compose 将避免重新组合 如果他们的输入没有改变的话。

考虑以下示例:

@Composable
fun LoginScreen(showError: Boolean) {
    if (showError) {
        LoginError()
    }
    LoginInput() // This call site affects where LoginInput is placed in Composition
}

@Composable
fun LoginInput() { /* ... */ }

Composable 函数的调用站点会影响智能重组,并且在 Composable 中具有 inline 关键字会将其子 Composable 调用站点设置为相同级别,而不是低一级。

对这里感兴趣的任何人是github repo 播放/测试重组

【讨论】:

  • 关于inline关键字的信息和文章真的很有帮助,谢谢提及!
猜你喜欢
  • 1970-01-01
  • 2022-11-25
  • 2021-06-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-06
  • 1970-01-01
相关资源
最近更新 更多