You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose Scoped/Smart Recomposition全量重组原因求解

Jetpack Compose 智能重组机制实验说明

我为了理解重组(recomposition)和智能重组(smart recomposition)机制做了实验并编写了示例:
动图演示
示例中的颜色通过Random.nextIn()生成,仅用于肉眼观测重组,颜色设置本身不影响重组逻辑,我也测试过未修改颜色的场景,现象一致。
动图内容分为三个部分:

Sample1

@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会触发重组。

Sample2

@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仅会更新它被观测的作用域,只有同时读取update2和update3的Text会在这两个状态更新时触发重组。

Sample3

@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()
                )

            }
        }
       // 🔥🔥 读取update1导致整个Composable触发重组
        Column(
            modifier = Modifier.background(getRandomColor())
        ) {
            println("☕️ Bottom Column")
            Text(
                text = "Update1: $update1",
                textAlign = TextAlign.Center,
                color = getRandomColor()
            )
        }
    }
}

Sample2和Sample3的唯一区别是Sample3底部的Text读取了update1状态,这一改动导致整个composable触发了全量重组,从动图中可以看到修改update1时Sample3的整体配色全部发生变化。

全量重组原因说明

该现象由Compose的重组作用域规则直接决定:

  • Compose的重组作用域是读取状态的最近非内联Composable函数/匿名Lambda块,而Column、Row、Box这类基础布局都是inline函数,本身不会成为独立的重组作用域。
  • Sample3中update1的读取位置在最外层Column的内部,外层Column是内联函数,因此读取update1的最近非内联作用域就是整个Sample3函数本身。
  • 当update1发生变化时,Compose会定位到它的读取作用域即整个Sample3触发重组,所有直接写在Sample3根作用域下的代码(比如根Column的background(getRandomColor())、println("ROOT")、顶部的Text("Sample3")等)都会被重新执行,因此会观测到全局配色变化。
  • 对比Sample2,update1的读取仅发生在第一个Button的content Lambda内部,该Lambda是独立的非内联作用域,所以状态变化仅会触发这个Lambda的重组,不会影响外层。

如果要避免全量重组,只要把读取update1的部分封装到一个独立的非内联Composable函数里即可,这样update1的读取作用域就被限制在这个新的子函数内部,状态变化不会触发外层Sample3全量重组。


内容的提问来源于stack exchange,提问作者Thracian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.23 20:54:00