Jetpack Compose 范围/智能重组

Thr*_*ian 16 android kotlin android-jetpack-compose compose-recomposition

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

\n

在此输入图像描述

\n

抱歉,颜色是用 Random.nextIn() 生成的,以便在视觉上观察重组,设置颜色对重组没有影响,也尝试不更改颜色。

\n

gif的内容由三部分组成

\n

样品1

\n
@Composable\nprivate fun Sample1() {\n\n    Column(\n        modifier = Modifier\n            .background(getRandomColor())\n            .fillMaxWidth()\n            .padding(4.dp)\n    ) {\n        var counter by remember { mutableStateOf(0) }\n\n\n        Text("Sample1", color = getRandomColor())\n\n        Button(\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(vertical = 4.dp),\n            colors = ButtonDefaults.buttonColors(backgroundColor = getRandomColor()),\n            onClick = {\n                counter++\n            }) {\n            Text("Counter: $counter", color = getRandomColor())\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我在这里没有问题,因为智能组合按预期工作,Text最重要的是不读取更改,counter因此重组仅发生在Textinside Button

\n

样品2

\n
@Composable\nprivate fun Sample2() {\n    Column(\n        modifier = Modifier.background(getRandomColor())\n    ) {\n\n        var update1 by remember { mutableStateOf(0) }\n        var update2 by remember { mutableStateOf(0) }\n\n        println("ROOT")\n        Text("Sample2", color = getRandomColor())\n\n        Button(\n            modifier = Modifier\n                .padding(start = 8.dp, end = 8.dp, top = 4.dp)\n                .fillMaxWidth(),\n            colors = ButtonDefaults.buttonColors(backgroundColor = getRandomColor()),\n            onClick = {\n                update1++\n            },\n            shape = RoundedCornerShape(5.dp)\n        ) {\n\n            println(" Button1\xef\xb8\x8f")\n\n            Text(\n                text = "Update1: $update1",\n                textAlign = TextAlign.Center,\n                color = getRandomColor()\n            )\n\n        }\n\n        Button(\n            modifier = Modifier\n                .padding(start = 8.dp, end = 8.dp, top = 2.dp)\n                .fillMaxWidth(),\n            colors = ButtonDefaults.buttonColors(backgroundColor = getRandomColor()),\n            onClick = { update2++ },\n            shape = RoundedCornerShape(5.dp)\n        ) {\n            println(" Button 2\xef\xb8\x8f")\n\n            Text(\n                text = "Update2: $update2",\n                textAlign = TextAlign.Center,\n                color = getRandomColor()\n            )\n        }\n\n        Column(\n            modifier = Modifier.background(getRandomColor())\n        ) {\n\n            println(" Inner Column")\n            var update3 by remember { mutableStateOf(0) }\n\n            Button(\n                modifier = Modifier\n                    .padding(start = 8.dp, end = 8.dp, top = 2.dp)\n                    .fillMaxWidth(),\n                colors = ButtonDefaults.buttonColors(backgroundColor = getRandomColor()),\n                onClick = { update3++ },\n                shape = RoundedCornerShape(5.dp)\n            ) {\n\n                println("\xe2\x9c\x85 Button 3\xef\xb8\x8f")\n                Text(\n                    text = "Update2: $update2, Update3: $update3",\n                    textAlign = TextAlign.Center,\n                    color = getRandomColor()\n                )\n\n            }\n        }\n\n        Column() {\n            println("\xe2\x98\x95\xef\xb8\x8f Bottom Column")\n            Text(\n                text = "Sample2",\n                textAlign = TextAlign.Center,\n                color = getRandomColor()\n            )\n        }\n\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

它也按预期工作,每个 mutableState 仅更新它们被观察到的范围。只有当这些 mutableState 中的任何一个更新时,它才会Text观察update2并更改。update3

\n

样品3

\n
@Composable\nprivate fun Sample3() {\n    Column(\n        modifier = Modifier.background(getRandomColor())\n    ) {\n\n\n        var update1 by remember { mutableStateOf(0) }\n        var update2 by remember { mutableStateOf(0) }\n\n\n        println("ROOT")\n        Text("Sample3", color = getRandomColor())\n\n        Button(\n            modifier = Modifier\n                .padding(start = 8.dp, end = 8.dp, top = 4.dp)\n                .fillMaxWidth(),\n            colors = ButtonDefaults.buttonColors(backgroundColor = getRandomColor()),\n            onClick = {\n                update1++\n            },\n            shape = RoundedCornerShape(5.dp)\n        ) {\n\n            println(" Button1\xef\xb8\x8f")\n\n            Text(\n                text = "Update1: $update1",\n                textAlign = TextAlign.Center,\n                color = getRandomColor()\n            )\n\n        }\n\n        Button(\n            modifier = Modifier\n                .padding(start = 8.dp, end = 8.dp, top = 2.dp)\n                .fillMaxWidth(),\n            colors = ButtonDefaults.buttonColors(backgroundColor = getRandomColor()),\n            onClick = { update2++ },\n            shape = RoundedCornerShape(5.dp)\n        ) {\n            println(" Button 2\xef\xb8\x8f")\n\n            Text(\n                text = "Update2: $update2",\n                textAlign = TextAlign.Center,\n                color = getRandomColor()\n            )\n        }\n\n        Column {\n\n            println(" Inner Column")\n            var update3 by remember { mutableStateOf(0) }\n\n            Button(\n                modifier = Modifier\n                    .padding(start = 8.dp, end = 8.dp, top = 2.dp)\n                    .fillMaxWidth(),\n                colors = ButtonDefaults.buttonColors(backgroundColor = getRandomColor()),\n                onClick = { update3++ },\n                shape = RoundedCornerShape(5.dp)\n            ) {\n\n                println("\xe2\x9c\x85 Button 3\xef\xb8\x8f")\n                Text(\n                    text = "Update2: $update2, Update3: $update3",\n                    textAlign = TextAlign.Center,\n                    color = getRandomColor()\n                )\n\n            }\n        }\n       //  Reading update1 causes entire composable to recompose\n        Column(\n            modifier = Modifier.background(getRandomColor())\n        ) {\n            println("\xe2\x98\x95\xef\xb8\x8f Bottom Column")\n            Text(\n                text = "Update1: $update1",\n                textAlign = TextAlign.Center,\n                color = getRandomColor()\n            )\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n
        Column(\n            modifier = Modifier.background(getRandomColor())\n        ) {\n            println("\xe2\x98\x95\xef\xb8\x8f Bottom Column")\n            Text(\n                text = "Update1: $update1",\n                textAlign = TextAlign.Center,\n                color = getRandomColor()\n            )\n        }\n    }\n
Run Code Online (Sandbox Code Playgroud)\n

Thr*_*ian 26

让智能重组范围发挥关键作用。您可以查看 Vinay Gaba 的What is \xe2\x80\x9cdonut-holeskipping\xe2\x80\x9d in Jetpack Compose? 文章。

\n

利兰·理查森 (Leland Richardson) 在这条推文中解释说

\n
\n

“甜甜圈孔跳过”部分是这样的事实:传递到可组合项(即 Button)的新 lambda\n 可以重新组合,而无需\n重新编译其余部分。事实上,lambda 是重构\n范围对于您能够做到这一点是必要的,但\n还不够

\n

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

\n

我们很早就想这样做,但认为这太复杂了,直到@chuckjaz 聪明地认识到,如果 lambda 是状态对象,并且调用是读取,那么这就是结果

\n
\n

您还可以在此处此处查看有关智能重组的其他答案。

\n

https://dev.to/zachklipp/scoped-recomposition-jetpack-compose-what-happens-when-state-changes-l78

\n

当读取状态时,它会在最近的范围内触发重组。而作用域是一个没有用 inline 标记并返回 Unit 的函数。Column、Row 和 Box 是内联函数,因此它们不创建作用域。

\n

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

\n
@Composable\nfun RandomColorColumn(content: @Composable () -> Unit) {\n\n    Column(\n        modifier = Modifier\n            .padding(4.dp)\n            .shadow(1.dp, shape = CutCornerShape(topEnd = 8.dp))\n            .background(getRandomColor())\n            .padding(4.dp)\n    ) {\n        content()\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

并更换了

\n
 Column(\n        modifier = Modifier.background(getRandomColor())\n    ) {\n        println("\xe2\x98\x95\xef\xb8\x8f Bottom Column")\n        Text(\n            text = "Update1: $update1",\n            textAlign = TextAlign.Center,\n            color = getRandomColor()\n        )\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

\n
    RandomColorColumn() {\n\n        println("\xe2\x98\x95\xef\xb8\x8f Bottom Column")\n        /*\n             Observing update(mutableState) does NOT causes entire composable to recompose\n         */\n        Text(\n            text = " Update1: $update1",\n            textAlign = TextAlign.Center,\n            color = getRandomColor()\n        )\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

只有这个范围才能按预期更新,并且我们可以进行智能重组。

\n

在此输入图像描述

\n

导致Text或任何 Composable 内部Column没有作用域,从而在 mutableState 值更改时被重构的原因是函数签名中Column具有inline关键字。

\n
@Composable\ninline fun Column(\n    modifier: Modifier = Modifier,\n    verticalArrangement: Arrangement.Vertical = Arrangement.Top,\n    horizontalAlignment: Alignment.Horizontal = Alignment.Start,\n    content: @Composable ColumnScope.() -> Unit\n) {\n    val measurePolicy = columnMeasurePolicy(verticalArrangement, horizontalAlignment)\n    Layout(\n        content = { ColumnScopeInstance.content() },\n        measurePolicy = measurePolicy,\n        modifier = modifier\n    )\n}\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

Compose 使用定义为的调用站点

\n
\n

调用站点是调用可组合项的源代码位置。\n 这会影响它在组合中的位置,从而影响\nUI 树。

\n

如果在重组期间,可组合项调用的可组合项与之前组合期间的可组合项不同,Compose 将识别\n调用了哪些可组合项或未调用哪些可组合项,并且对于在两个组合中调用的可组合项,\n如果它们的输入\n,Compose 将避免重新组合它们没有改变。

\n
\n

考虑以下示例:

\n
@Composable\nfun LoginScreen(showError: Boolean) {\n    if (showError) {\n        LoginError()\n    }\n    LoginInput() // This call site affects where LoginInput is placed in Composition\n}\n\n@Composable\nfun LoginInput() { /* ... */ }\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

对于任何对此感兴趣的人来说,可以使用github 存储库来玩/测试重组

\n

  • 有关内联关键字和文章的信息非常有帮助,感谢您的提及! (4认同)