Jetpack Compose中的remember实际上是做什么的?它在内部是如何工作的?

64

查看 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) }用于存储状态/数值。我的问题是,它在幕后是如何工作的。如果没有使用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中的值,remember只能与MutableState一起使用吗?


1
你可以阅读我的文章了解它是做什么的以及它是如何实现的:https://medium.com/@thegoldycopythat/making-sense-of-compose-magic-deep-dive-b03873910a67 - Abhinav Chauhan
@AbhinavChauhan 我已经阅读了你的文章,非常好。你可以根据你的文章添加一个答案。 - Thracian
谢谢,我会抽时间添加答案,感谢建议,您也可以在Medium上关注以下内容。 - Abhinav Chauhan
非常有帮助,无论是问题还是答案。谢谢。 - Joan P.
4个回答

83

remember - 允许您记住来自先前重新组合调用和仅此的状态。因此,如果您在初始运行时随机化颜色,则随机化的颜色仅会计算一次,并在以后需要重新组合时重复使用。

因此,remember = 仅在重新组合被调用时存储该值。

现在,第二个重要的事情是知道何时应该实际触发reCompose,这时可变状态就有所帮助了。

mutablestate = 存储该值,并在我更新值触发时,为使用此数据的所有元素重新组合。


5
从我阅读的Compose材料中,“remember”非常令人困惑。我得到的印象是,为了使可组合体重新组合,必须记住状态变量并在该可组合体中使用它。现在我明白这不是这样的。只要可组合体读取我在某个地方(内部或外部任何可组合代码)声明的可变状态,并且只要状态发生变化,可组合体就会重新组合。“remember”只是用于一些简单的事情,比如在可组合体内点击按钮并增加一个数字,否则,在大多数情况下,它并不需要。 - LXJ

48
要了解组合和重新组合的工作原理,您可以查看Leland Richardson的Jetpack Compose文章内幕,其中非常好地描述了内部工作,还有youtube视频这里。本答案大部分使用该文章作为参考,并从中引用了大部分内容。
Composer的实现包含一个数据结构,与间隙缓冲区密切相关。这种数据结构通常用于文本编辑器。
间隙缓冲区表示具有当前索引或光标的集合。它在内存中使用平面数组实现。该平面数组比其表示的数据集合更大,未使用的空间称为间隙。
基本上,在Composable函数插槽表附近添加空间以能够动态更新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()
}

当这个composer执行时,它会执行以下操作:

  1. 调用Composer.start并存储一个group对象
  2. remember插入一个group对象
  3. mutableStateOf返回的值,即state实例,被存储。
  4. Button存储一个group,然后是每个参数。
  5. 最后我们到达composer.end。

enter image description here

数据结构现在包含了组合中的所有对象,整个树以执行顺序存储,实际上是树的深度优先遍历。

所以,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)用于向Compose编译器传达某些关于特定类型或函数行为的保证。
当应用于类或接口时,稳定性表示以下内容必须为真: 1. equals的结果对于相同的两个实例始终返回相同的结果。 2. 当类型的公共属性更改时,将会通知组合。 3. 所有公共属性类型都是稳定的。
当应用于函数或属性时,Stable注释表示如果传入相同的参数,则函数将返回相同的结果。这仅在参数和结果本身是稳定的、不可变的或原始的情况下才有意义。
此注释所暗示的不变量被Compose编译器用于优化,并且如果上述假设不满足,则具有未定义的行为。因此,除非确定满足这些条件,否则不应使用此注释。
另一个带有视频源描述Compose如何工作的来源。

2
讲解得非常清晰!这应该是被采纳的答案! - sud007

12

代码实验室示例提到了关于remembermutableState的内容:

响应状态变化是Compose的核心。通过调用可组合函数,Compose将数据转换为UI。如果数据发生更改,您可以使用新数据重新调用这些函数,从而创建更新的UI。 Compose提供了观察应用程序数据变化的工具,这将自动重新调用您的函数--这称为重新组合。 Compose还会查看单个组件需要的数据,以便只需要重新组合已更改数据的组件,并跳过未受影响的组件。

在幕后,Compose使用自定义的Kotlin编译器插件,因此当基础数据发生更改时,可组合函数可以被重新调用以更新UI层次结构。

要向可组合项添加内部状态,请使用mutableStateOf函数,该函数为组合项提供可变内存。为了不为每个重新组合都拥有不同的状态,请使用remember来记住可变状态。如果在屏幕上的不同位置有多个组合项的实例,则每个副本将获取其自己版本的状态。您可以将内部状态视为类中的私有变量。

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

第一种情况适用于对象不需要在每次重新组合时实例化,并且存在另一个触发器来触发组合的情况。

例如,remember{Paint()},任何不需要多次实例化或内存占用较大的对象。如果具有此对象的可组合项被重新组合,则由于使用了remember,您的对象的属性不会更改;如果您不使用remember,则您的对象将在每次重新组合时实例化,并且之前设置的所有属性都将重置。

如果您需要一个trigger(mutableStateOf)并需要最新的值(remember),就像在问题中选择remember{mutableStateOf()}一样。


4

每次 composition 都会清除变量。

使用 remember 可以获取上一个值。

我认为在 ViewModel 中声明一个 mutableState 是等价的。


1
有代码片段的示例吗? - IgorGanapolsky

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接