Jetpack Compose 记住实际上做了什么,它是如何工作的?

What does Jetpack Compose remember actually do, how does it work under the hood?

查看 Codelab 的基础教程,有一个片段可以在点击按钮时增加计数器

@Composable
fun MyScreenContent(names: List<String> = listOf("Android", "there")) {
    val counterState = remember { mutableStateOf(0) }

    Column(modifier = Modifier.fillMaxHeight()) {
        Column(modifier = Modifier.weight(1f)) {
            for (name in names) {
                Greeting(name = name)
                Divider(color = Color.Black)
            }
        }
        Counter(
            count = counterState.value,
            updateCount = { newCount ->
                counterState.value = newCount
            }
        )
    }
}


@Composable
fun Counter(count: Int, updateCount: (Int) -> Unit) {
    Button(
        onClick = { updateCount(count + 1) },
        colors = ButtonConstants.defaultButtonColors(
            backgroundColor = if (count > 5) Color.Green else Color.White
        )
    ) {
        Text("I've been clicked $count times")
    }
}

显然remember { mutableStateOf(0) }存储了state/value。我的问题是 remember 在幕后做了什么。在没有 的情况下使用 var count = remember { 0 }mutableStateOf(0) 记住 不会增加值。

fun MyScreenContent(names: List<String> = listOf("Android", "there")) {
   
    var count = remember { 0 }

    Column(modifier = Modifier.fillMaxHeight()) {
        Column(modifier = Modifier.weight(1f)) {
            for (name in names) {
                Greeting(name = name)
                Divider(color = Color.Black)
            }
        }
        Counter(
            count = count,
            updateCount = { newCount ->
                count = newCount
            }
        )
    }
}

上面的代码片段不会更新打印在 Text 上的值,记得只适用于 MutableState 吗?

remember - 允许您记住之前重组调用的状态,仅此而已。 因此,例如,如果您在初始 运行 处随机化颜色。随机颜色将被计算一次,并在需要重新组合时重复使用。

所以... 记住 = 存储值以防调用重组。

现在第二件事是知道什么时候应该真正触发重新组合。 可变状态会提供帮助。

mutablestate = 存储值,以防万一我更新值触发重组使用此数据的所有元素。

Codelab example 提及 remembermutableState as

Reacting to state changes is at the very heart of Compose. Compose apps transform data into UI by calling Composable functions. If your data changes, you recall these functions with the new data, creating an updated UI. Compose offers tools for observing changes in your app's data, which will automatically recall your functions—this is called recomposing. Compose also looks at what data is needed by an individual composable so that it only needs to recompose components whose data has changed and can skip composing those that are not affected.

Under the hood, Compose uses a custom Kotlin compiler plugin so when the underlying data changes, the composable functions can be re-invoked to update the UI hierarchy.

To add internal state to a composable, use the mutableStateOf function, which gives a composable mutable memory. To not have a different state for every recomposition, remember the mutable state using remember. And, if there are multiple instances of the composable at different places on the screen, each copy will get its own version of the state. You can think of internal state as a private variable in a class.

remember{X}remember{mutableStateOf(X)} 在不同的场景下很有用。

当您的对象不需要在每次重组时实例化时,第一个是必需的,还有另一个触发组合的触发器。

这方面的一个例子是 remember{Paint()},任何不需要多次实例化的对象或实例化需要大量内存的对象。如果拥有此对象的可组合项被重组,由于 remember,您的对象的属性不会改变,如果您不使用 remember,您的对象将在每次重组时实例化,并且所有属性都是先前设置的已重置。

如果您需要 trigger(mutableStateOf) 并且需要最新值(记住),请选择 remember{mutableStateOf()}

要了解合成和重组的工作原理,您可以查看 Under the hood of Jetpack Compose article by Leland Richardson, which describes inner works very well, also youtube video here。而且这个回答大部分都是参考文章,引用的最多。

Composer 的实现包含一个与 Gap Buffer 密切相关的数据结构。这种数据结构常用于文本编辑器。

间隙缓冲区表示具有当前索引或游标的集合。它是在内存中用平面数组实现的。该平面数组大于它所代表的数据集合,未使用的 space 称为间隙。

基本上是在您的可组合函数插槽 table 附近添加 space 以便能够以高成本动态更新 UI,因为 getmoveinsertdelete — 是恒定时间操作,除了移动间隙。移动差距是 O(n) 但这并不经常发生,您需要更改所有 UI 结构,平均而言,UI 不会更改结构非常。

@Composable
    fun Counter() {
     var count by remember { mutableStateOf(0) }
     Button(
       text="Count: $count",
       onPress={ count += 1 }
     )
    }

当编译器看到 Composable 注释时,它会插入额外的参数并调用函数的主体。 首先,编译器添加对 composer.start 方法的调用,并将编译时生成的密钥整数传递给它。

fun Counter($composer: Composer) {
 $composer.start(123)
 var count by remember($composer) { mutableStateOf(0) }
 Button(
   $composer,
   text="Count: $count",
   onPress={ count += 1 },
 )
 $composer.end()
}

当此作曲家执行时,它会执行以下操作:

  1. Composer.start 被调用并存储一个组对象
  2. 记得插入组对象
  3. 状态实例 mutableStateOf returns 存储的值。
  4. 按钮存储一个组,后面是它的每个参数。
  5. 然后我们终于到达 composer.end。

数据结构现在保存组合中的所有对象,按执行顺序排列的整个树,有效地对树进行深度优先遍历

因此 remember 需要存储一个 mutableState() 以从之前的组合中获取值,并且 mutableState() 需要触发一个。

并且MutableState接口使用了@Stable注解

@Stable
interface MutableState<T> : State<T> {
    override var value: T
    operator fun component1(): T
    operator fun component2(): (T) -> Unit
}

Stable is used to communicate some guarantees to the compose compiler about how a certain type or function will behave.

When applied to a class or an interface, Stable indicates that the following must be true:

  1. The result of equals will always return the same result for the same two instances.
  2. When a public property of the type changes, composition will be notified.
  3. All public property types are stable. When applied to a function or a property, the Stable]annotation indicates that the function will return the same result if the same parameters are passed in. This is only meaningful if the parameters and results are themselves Stable, Immutable, or primitive.

The invariants that this annotation implies are used for optimizations by the compose compiler, and have undefined behavior if the above assumptions are not met. As a result, one should not use this annotation unless they are certain that these conditions are satisfied.

描述 Compose 工作原理的另一个 source with a Video