Kotlin Compose 之 remember 关键字详解

remember 是 Jetpack Compose 中一个重要的工具,用于在组合期间保存状态,以确保在重新组合时状态保持不变。接下来,我们将详细讲解 remember 的用法、其内部实现原理,多个参数时缓存无效化的判断,currentComposer 的具体实现及其上下文含义,以及使用 remember 的场景和原因。

1. remember 的作用

remember 用于在 Jetpack Compose 中保存可组合函数在重组期间的状态。它通过在组合中缓存计算结果,确保每次重组时状态保持不变。

2. 基本用法

kotlin 复制代码
@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }

    Button(onClick = { count++ }) {
        Text("Count: $count")
    }
}

在这个例子中,remember 确保 count 状态在 Counter 组件的每次重组中保持不变。

3. 特殊场景

3.1 记住复杂对象
kotlin 复制代码
@Composable
fun ListExample() {
    val items = remember { mutableStateListOf("Item 1", "Item 2") }

    Column {
        items.forEach { item ->
            Text(item)
        }
        Button(onClick = { items.add("Item ${items.size + 1}") }) {
            Text("Add Item")
        }
    }
}

点击按钮,列表增加一个新项,显示的列表更新。

3.2 记住不可变状态

kotlin 复制代码
@Composable
fun ExpensiveComputation() {
    val result = remember { expensiveComputation() }

    Text("Result: $result")
}

fun expensiveComputation(): Int {
    return (1..100000).sum()
}

初次组合时执行 expensiveComputation,结果显示在文本中,后续重组不会重复计算。

4. 源码实现详解

remember 的源码位于 Compose 运行时库中,核心实现如下:

kotlin 复制代码
@Composable
fun <T> remember(vararg inputs: Any?, calculation: @DisallowComposableCalls () -> T): T {
    val composer = currentComposer
    return composer.cache(false) {
        calculation()
    }
}
4.1 calculation

calculation 是一个 lambda 表达式,当缓存无效或缓存不存在时,calculation 会被执行,并且其返回值会被存储在缓存中供后续使用。

5. 关键方法解析

5.1 currentComposer

currentComposer 是一个顶层属性,用于获取当前的 Composer 实例:

csharp 复制代码
val currentComposer: Composer
    get() = error("This should have been replaced by the compiler")

Compose 编译器会将对 currentComposer 的访问替换为当前组合上下文的实际引用。具体来说,currentComposer 代表当前正在进行组合操作的 Composer 实例。

5.2 Composer.cache

cache 方法用于在组合过程中缓存值,确保在重组时可以重用已计算的值,而不必重新计算:

  • 获取当前键: val key = currentKey() 用于获取当前组合位置的唯一键。
  • 检查缓存: val value = cache[key] 检查缓存中是否有对应键的值。
  • 判断是否需要重新计算: if (invalidate || value == null) 判断缓存是否无效或不存在。
  • 执行计算: val result = block() 执行传入的计算逻辑 block(即 calculation)。
  • 缓存结果: cache[key] = result 将计算结果存储到缓存中。
  • 返回结果: return resultreturn value as T 返回计算结果或缓存值。
kotlin 复制代码
inline fun <T> Composer.cache(invalidate: Boolean, block: () -> T): T {
    val key = currentKey()
    if (invalidate || !cache.containsKey(key)) {
        cache[key] = block()
    }
    return cache[key] as T
}

private fun currentKey(): Int {
    // 返回当前组合位置的唯一键
}

6. 缓存无效化

remember 传入多个参数时,缓存无效化的判断依赖于这些参数的变化。当任意一个参数发生变化时,缓存将被无效化,重新计算结果。

kotlin 复制代码
@Composable
fun Greeting(name: String, age: Int) {
    val message = remember(name, age) { "Hello $name, you are $age years old!" }

    Text(message)
}

在这个例子中,如果 nameage 发生变化,remember 将重新计算 message

6.1 多参数缓存无效化的实现

remember 的内部实现中,会将传入的 inputs 作为 cache 方法的参数,当 inputs 发生变化时,缓存将被无效化:

kotlin 复制代码
@Composable
fun <T> remember(vararg inputs: Any?, calculation: @DisallowComposableCalls () -> T): T {
    val composer = currentComposer
    return composer.cache(inputs.isNotEmpty()) {
        calculation()
    }
}

composer.cache 方法会检查传入的 inputs 是否发生变化。如果变化,则无效化缓存,并执行 calculation 重新计算结果。

inputsChanged 是通过比较新旧输入参数列表来检测变化的。当 remember 函数被调用时,新的参数列表与上一次组合时的参数列表进行比较,如果不同,则标记为变化

kotlin 复制代码
class Composer {
    private var previousInputs: Array<Any?>? = null
    private val cache = mutableMapOf<Int, Any?>()
    private var currentKey = 0

    inline fun <T> cache(inputsChanged: Boolean, block: () -> T): T {
        val key = currentKey++
        val value = cache[key]
        if (inputsChanged || value == null) {
          //inputsChanged 用于判断输入参数是否变化
          //如果输入参数变化或缓存不存在,执行 block 并更新缓存
            val result = block()
            cache[key] = result
            return result
        }
        return value as T
    }
    //判断是否变化
    fun changed(vararg inputs: Any?): Boolean {
        val previous = previousInputs
        if (previous == null || !previous.contentEquals(inputs)) {
            previousInputs = inputs.copyOf()
            return true
        }
        return false
    }
}

7. 使用场景

7.1 保存局部状态

remember 常用于保存可组合函数的局部状态,确保状态在重组期间保持不变。

kotlin 复制代码
@Composable
fun Example() {
    val state = remember { mutableStateOf(0) }

    // 使用 state
}
7.2 记住计算结果

对于需要记住的计算结果,可以使用 remember 缓存计算结果,避免在每次重组时重新计算。

kotlin 复制代码
@Composable
fun Example() {
    val result = remember { expensiveComputation() }

    // 使用 result
}
7.3 记住不可变状态

对于不可变状态,也可以使用 remember 进行缓存。

kotlin 复制代码
@Composable
fun Example() {
    val state = remember { immutableState() }

    // 使用 state
}

为什么需要记住不可变状态?:

  1. 性能优化:即使状态不可变,初始化或创建这些状态可能是一个耗时操作。使用 remember 可以避免在每次重组时重新创建这些对象,提升性能。
  2. 代码简洁:保持状态的一致性和可重复性,有助于使代码更简洁、易读。

8. 总结

remember 是 Jetpack Compose 中用于在组合过程中保存状态的重要工具。它通过缓存计算结果,确保在重组期间状态保持不变。理解 remember 的内部实现和应用场景,可以帮助开发者更好地管理和优化应用状态,提升应用性能。

sequenceDiagram participant Developer as 开发者 participant Composer as Compose 框架 participant Cache as 缓存 Developer->>Composer: 调用 remember Composer->>Cache: 检查缓存 Cache-->>Composer: 返回缓存值(如果存在) Composer->>Developer: 返回缓存值(如果存在) Composer->>Cache: 执行计算并缓存结果(如果不存在) Cache-->>Composer: 返回新缓存值 Composer->>Developer: 返回新缓存值
相关推荐
赏金术士20 分钟前
第六章:UI组件与Material3主题
android·ui·kotlin·compose
TechMerger2 小时前
Android 17 重磅重构!服役 20 年的 MessageQueue 迎来无锁改造,卡顿大幅优化!
android·性能优化
yuhuofei20214 小时前
【Python入门】Python中字符串相关拓展
android·java·python
dalancon4 小时前
Android Input Spy Window
android
dalancon6 小时前
InputDispatcher派发事件,查找目标窗口
android
我命由我123456 小时前
Android Framework P3 - MediaServer 进程、认识 ServiceManager 进程
android·c语言·开发语言·c++·visualstudio·visual studio·android runtime
天才少年曾牛7 小时前
Android14 新增系统服务后,应用调用出现 “hidden api” 警告的原因与解决方案
android·frameworks
赏金术士7 小时前
Jetpack Compose 底部导航实战教程(完整版)
android·kotlin·compose
随遇丿而安7 小时前
第5周:XML 资源、样式和主题,真正解决的是“页面以后还改不改得动”
android
zh_xuan8 小时前
Android 获取系统内存页大小:sysconf(_SC_PAGESIZE) 与 JNI 实现
android·jni·ndk·内存页大小