Android Compose 中的哑重组

问题描述 投票:0回答:3

考虑这个最小的代码片段(Kotlin 中):

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import java.time.LocalDateTime
import java.util.*

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {

            var time by remember {
                mutableStateOf("time")
            }
            Column(modifier = Modifier.clickable { time = LocalDateTime.now().toString() }) {
                Text(text = UUID.randomUUID().toString())
                Text(text = time)
            }
        }
    }
}

检查上面的代码,从逻辑角度来看,我们预计在单击

Column
时,由于只有参数
time
发生变化,因此只有较低时间
Text
可组合项会被重绘。这是因为 重组会尽可能跳过

但是,人们发现上面的

Text
可组合项也被重绘(显示的 UUID 不断变化)。

  1. 这是为什么?

请注意,我的

Column
可组合项的非幂等性应该没有影响,除非重画是愚蠢的。

android android-layout android-jetpack-compose compose-recomposition
3个回答
1
投票

您可以尝试运行这段代码

@Composable
fun IdempotenceTest() {    
    var time by remember {
        mutableStateOf("time")
    }
    Column(
        modifier = Modifier.clickable {
            time = LocalDateTime.now().toString()
        }
    ) {
        Text(text = getRandomUuid())
        TestComposable(text = returnSameValue())
        Text(text = time)
    }
}

@Composable
fun TestComposable(text: String) {
    SideEffect {
        Log.d(TAG, "TestComposable composed with: $text")
    }
    Text(text = text)
}

private fun getRandomUuid(): String {
    Log.d(TAG, "getRandomUuid: called")
    return UUID.randomUUID().toString()
}

private fun returnSameValue(): String {
    Log.d(TAG, "returnSameValue: called")
    return "test"
}

如果您检查日志,您会发现每次状态更改时,编译器都会尝试重新调用正在读取状态值的最小封闭 lamda/函数。因此, IdempotenceTest 函数(在我的例子中和你的例子中的 setContent{} lamda )将被重新执行,它将调用

getRandomUuid
returnSameValue
函数,并根据它们返回的值,它将决定是否重新组合依赖于这些返回值的元素。如果您想防止计算一次又一次地发生,请将其包装在
remember{}
块中。

现在,如果您使用

Button
代替 Column,您会发现只有相同的内容 lamda 才会被执行。发生这种情况的原因是 Column 是内联函数,而 Button 本身使用了内部的 Surface,这是非内联的。因此,
Column{}
的内容被复制到封闭的功能块内,从而导致整个
IdempotenceTest
对于重新组合无效。

顺便说一句,可组合函数必须无副作用,以确保幂等性。您可以在这里阅读更多内容。

要了解有关重组范围的更多信息,您可以参阅博客文章这里这里


0
投票

rememberSavable {...}
为我工作。

它允许您直接从

clickable
更新状态并触发重组:

...
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
...

var time by rememberSaveable { mutableStateOf("time") }
Column(
    modifier = Modifier.clickable { 
        time = LocalDateTime.now().toString() 
    }
) {
    Text(text = UUID.randomUUID().toString())
    Text(text = time)
}

0
投票

这是因为 Modifier.clickable lambda 每次都会返回一个新实例。这将导致重组。只需将修饰符包装在记住块中即可。

val myModifier = remember { 
            Modifier.clickable { time = LocalDateTime.now().toString() } 
        }

Column(modifier = myModifier){
...
}
最新问题
© www.soinside.com 2019 - 2025. All rights reserved.