Android Compose 中的哑重组
Dumb recomposition in Android Compose
考虑这个最小的代码片段(在 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
可组合项是重新绘制。这是因为 recomposition skips as much as possible.
然而,发现上面的 Text
可组合项也在重绘(显示的 UUID 一直在变化)。
- 这是为什么?
请注意,我的 Column
可组合项的非幂等性应该没有影响,除非重绘是愚蠢的。
你可以试试运行这段代码
@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/function。因此,IdempotenceTest 函数(在我的例子中和 setContent{} lamda 在你的例子中)将被重新执行,这将调用 getRandomUuid
和 returnSameValue
函数并基于 returned 的值这些,它将决定是否重新组合依赖于这些 return 值的元素。如果你想防止计算一次又一次地发生,把它包装在一个 remember{}
块中。
现在,如果您要使用 Button
来代替列,您会发现只有相同的内容 lamda 会被执行。发生这种情况的原因是 Column 是一个内联函数,而 Button 本身在其内部使用非内联的 Surface。因此 Column{}
的内容被复制到封闭的函数块中,从而导致整个 IdempotenceTest
因重新组合而无效。
作为旁注,可组合函数必须无副作用以确保幂等性。您可以阅读更多 here.
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)
}
考虑这个最小的代码片段(在 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
可组合项是重新绘制。这是因为 recomposition skips as much as possible.
然而,发现上面的 Text
可组合项也在重绘(显示的 UUID 一直在变化)。
- 这是为什么?
请注意,我的 Column
可组合项的非幂等性应该没有影响,除非重绘是愚蠢的。
你可以试试运行这段代码
@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/function。因此,IdempotenceTest 函数(在我的例子中和 setContent{} lamda 在你的例子中)将被重新执行,这将调用 getRandomUuid
和 returnSameValue
函数并基于 returned 的值这些,它将决定是否重新组合依赖于这些 return 值的元素。如果你想防止计算一次又一次地发生,把它包装在一个 remember{}
块中。
现在,如果您要使用 Button
来代替列,您会发现只有相同的内容 lamda 会被执行。发生这种情况的原因是 Column 是一个内联函数,而 Button 本身在其内部使用非内联的 Surface。因此 Column{}
的内容被复制到封闭的函数块中,从而导致整个 IdempotenceTest
因重新组合而无效。
作为旁注,可组合函数必须无副作用以确保幂等性。您可以阅读更多 here.
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)
}