Jetpack Compose 记得实际上做了什么,它是如何在引擎盖下工作的?

Thr*_*ian 7 android android-jetpack-compose

查看 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")
    }
}
Run Code Online (Sandbox Code Playgroud)

很明显,remember { mutableStateOf(0) }存储状态/值。我的问题是记忆在幕后做了什么。使用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
            }
        )
    }
}
Run Code Online (Sandbox Code Playgroud)

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

Thr*_*ian 53

要了解合成和重组的工作原理,您可以查看Leland Richardson 撰写的Under the hood of Jetpack Compose 文章,该文章很好地描述了内部工作原理,还有 YouTube视频。这个答案大部分都使用文章作为参考,并引用了其中最多的内容。

\n

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

\n

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

\n

基本上在可组合函数槽表附近添加空间,以便能够以高成本动态更新 UI,因为getmoveinsertdelete\xe2\x80\x94 是恒定时间操作,除了移动间隙之外。移动间隙的时间复杂度为O(n),但这种情况并不经常发生,您需要更改所有 UI 结构,平均而言,UI 不会改变结构太多。

\n
@Composable\n    fun Counter() {\n     var count by remember { mutableStateOf(0) }\n     Button(\n       text="Count: $count",\n       onPress={ count += 1 }\n     )\n    }\n
Run Code Online (Sandbox Code Playgroud)\n

当编译器看到 Composable 注释时,它会插入附加参数并调用函数体。\n首先,编译器添加对该方法的调用composer.start,并向其传递一个编译时生成的关键整数。

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

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

\n
    \n
  1. Composer.start 被调用并存储一个组对象
  2. \n
  3. 记住插入一个组对象
  4. \n
  5. 存储 mutableStateOf 返回的值(状态实例)。
  6. \n
  7. Button 存储一个组,后跟其每个参数。
  8. \n
  9. 最后我们到达了composer.end。
  10. \n
\n

在此输入图像描述

\n

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

\n

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

\n

并且MutableState接口使用@Stable注解

\n
@Stable\ninterface MutableState<T> : State<T> {\n    override var value: T\n    operator fun component1(): T\n    operator fun component2(): (T) -> Unit\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

Stable 用于向 compose 编译器传达有关特定类型或函数的行为方式的一些保证。

\n

当应用于类或接口时,Stable 表明以下条件必须为真:

\n
    \n
  1. 对于相同的两个实例,equals 的结果将始终返回相同的结果。
  2. \n
  3. 当类型的公共属性发生变化时,组合将会得到通知。
  4. \n
  5. 所有公共属性类型都是稳定的。\n当应用于函数或属性时,[稳定]注释表示如果传入相同\n参数,该函数将返回相同的结果。仅当参数\n和结果为它们本身是稳定的、不可变的或原始的。
  6. \n
\n

此注释暗示的不变量由 compose 编译器用于优化,并且如果不满足上述假设,则具有未定义的行为。因此,除非确定满足这些条件,否则不应使用此注释。

\n
\n

另一个带有描述 Compose 如何工作的视频的来源。

\n

  • 解释得很清楚!这应该是公认的答案! (2认同)

Thr*_*ian 14

Codelab 示例提到了RemembermutableState作为

\n
\n

对状态变化做出反应是 Compose 的核心。Compose\napps 通过调用可组合函数将数据转换为 UI。如果您的数据发生变化,您可以使用新数据调用这些功能,从而创建更新的 UI。Compose 提供了用于观察应用程序数据变化的工具,它会自动调用您的函数\xe2\x80\x94,这\n称为重构。Compose 还会查看单个可组合项\n需要哪些数据,以便它只需要\n重新组合数据已更改的组件,\n并可以跳过组合那些不受影响的\n组件。

\n

在底层,Compose 使用自定义 Kotlin 编译器插件,因此当底层数据发生变化时,可以重新调用可组合函数来更新 UI 层次结构。

\n

要将内部状态添加到可组合项,请使用 mutableStateOf\n 函数,它提供可组合的可变内存。为了避免每次重组都有不同的状态,请使用 Remember 来记住可变状态。而且,如果屏幕上不同位置有多个可组合实例,则每个副本都将获得其自己的状态版本。您可以将内部状态视为类中的私有变量。

\n
\n

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

\n

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

\n

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

\n

如果您需要trigger(mutableStateOf) 并且需要具有最新值(记住),就像有问题一样,请选择remember{mutableStateOf()}

\n


Dab*_*ber 12

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

所以......记住=存储值以防recompose被调用。

现在第二件事是知道何时应该真正触发重新编写。并且有可变状态来提供帮助。

mutablestate = 存储值 AND 以防我使用此数据更新所有元素的值触发器重构。

  • 从我读到的有关 Compose 的材料来看,“记住”非常令人困惑。我的印象是,为了重构可组合项,必须记住状态变量并在该可组合项中使用状态变量。现在我明白事实并非如此。只要可组合项读取我在某处声明的可变状态(在任何可组合代码的内部或外部),并且只要状态发生变化,可组合项就会重新组合。“记住只是为了在可组合​​项中执行一些简单的操作,例如单击按钮并增加数字,否则,在我的大多数情况下都不需要它。 (5认同)