StateFlow 中update的用法

在 Kotlin Coroutines 中,MutableStateFlow 是用于管理状态的核心 Hot Flow。更新状态主要有 value 赋值 、emit 、update 、getAndUpdate 、updateAndGet 和 compareAndSet 等方法。它们的核心区别在于 并发安全性 、原子性 和 返回值。

一、update 函数详解

作用 :基于旧值 计算新值 ,并通过 CAS (Compare-And-Swap) 机制保证原子性 与线程安全 ,彻底解决竞态条件 (Race Condition) 。

用法:

ini 复制代码
// 1. 基本数据类型(计数)
_counter.update { prevCount -> prevCount + 1 }

// 2. 数据类(更新状态对象,必须使用.copy())
_uiState.update { currentState ->
        currentState.copy(
                isLoading = false,
                data = newData
        )
}

底层原理(伪代码) :

kotlin

kotlin 复制代码
public inline fun <T> MutableStateFlow<T>.update(transform: (T) -> T) {
    while (true) {
        val prevValue = value       // 1. 读取当前值
        val nextValue = transform(prevValue) // 2. 基于旧值计算新值
        if (compareAndSet(prevValue, nextValue)) { // 3. 原子比较并替换
            return // 成功则退出,失败则循环重试
        }
    }
}
  • 核心优势 :读取 - 修改 - 写入 三步操作是原子不可分割的。多协程 / 多线程并发修改时,不会丢失更新。

二、其他核心更新函数对比

1. value 赋值 (直接 Setter)

  • 语法 :mutableStateFlow.value = newValue

  • 特点:

    • 非原子 :先读 (value) 后写是两步独立操作。
    • 简单直接 :适合与旧值无关的覆盖性更新。
    • 并发风险 :多协程同时 value = value + 1 会导致结果错误。
  • 适用场景:状态重置、初始化、或单协程修改。

2. emit(value: T) (挂起函数)

  • 语法 :suspend fun emit(value: T)

  • 特点:

    • 内部实现就是 value = newValue。
    • 是 挂起函数,必须在协程中调用。
    • 主要为了兼容 Flow 接口的通用写法。
  • 适用场景 :需要与其他 Flow 操作符(如 transform)混用,或强调语义为 "发射事件" 时。

3. getAndUpdate

  • 语法 :fun getAndUpdate(transform: (T) -> T): T

  • 特点:

    • 与 update 一样原子安全。
    • 返回值 :返回更新前的旧值。
  • 适用场景:需要在更新的同时,获取并使用旧值进行日志记录或其他计算。

4. updateAndGet

  • 语法 :fun updateAndGet(transform: (T) -> T): T

  • 特点:

    • 与 update 一样原子安全。
    • 返回值 :返回更新后的新值。
  • 适用场景:需要立即使用刚刚更新成功的新值。

5. compareAndSet(expect: T, update: T): Boolean

  • 语法 :fun compareAndSet(expect: T, update: T): Boolean

  • 特点:

    • 底层原语 :update 系列函数的基石。
    • 逻辑 :仅当当前值 等于 expect 时,才将其设置为 update。
    • 返回值 :true (成功) / false (失败)。
  • 适用场景 :需要手动控制更新条件,实现更复杂的并发逻辑。

三、总对比表

函数 / 操作 并发安全 原子性 返回值 基于旧值 适用场景
value = x ❌ 不安全 ❌ 非原子 Unit ❌ 不依赖 单协程、覆盖赋值、重置
emit(x) ❌ 不安全 ❌ 非原子 Unit ❌ 不依赖 兼容 Flow 接口、挂起上下文
update { ... } ✅ 安全 ✅ 原子 Unit ✅ 是 首选! 所有基于旧值的计算(如计数、状态修改)
getAndUpdate { ... } ✅ 安全 ✅ 原子 旧值 ✅ 是 需要使用旧值
updateAndGet { ... } ✅ 安全 ✅ 原子 新值 ✅ 是 需要立即使用新值
compareAndSet(e, u) ✅ 安全 ✅ 原子 Boolean ✅ 是 底层控制、复杂条件更新

四、最佳实践建议

  1. 优先使用 update {} :只要新值依赖旧值(new = f(old)),无论是否并发 ,都用 update。它是最安全、最简洁的选择。

    kotlin

    ini 复制代码
    // 推荐
    _uiState.update { it.copy(isLoading = true) }
    // 不推荐(有竞态风险)
    // _uiState.value = _uiState.value.copy(isLoading = true)
  2. 使用数据类 + copy() :StateFlow 按 值相等 (equals()) 发射。修改对象属性时,必须生成新实例才能通知观察者。

  3. value 仅用于赋值 :value 属性主要用于直接读取当前值 或无条件覆盖。

五、代码示例

kotlin

kotlin 复制代码
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

data class UiState(val count: Int = 0, val isLoading: Boolean = false)

fun main() = runBlocking {
    val _uiState = MutableStateFlow(UiState())

    // 1. 使用 update (最推荐)
    launch {
        repeat(5) {
            _uiState.update { it.copy(count = it.count + 1) }
            delay(100)
        }
    }

    // 2. 使用 getAndUpdate (获取旧值)
    launch {
        delay(250)
        val oldState = _uiState.getAndUpdate { it.copy(isLoading = true) }
        println("更新前状态: $oldState")
    }

    // 3. 使用 updateAndGet (获取新值)
    launch {
        delay(300)
        val newState = _uiState.updateAndGet { it.copy(isLoading = false) }
        println("更新后状态: $newState")
    }

    // 监听结果
    _uiState.collect {
        println("当前状态: $it")
    }
}
相关推荐
alexhilton4 天前
提升 Android Compose 页面的流畅度
android·kotlin·android jetpack
MiyamuraMiyako4 天前
Compose LazyRow 实现尾项露出与少量数据均分
android·android jetpack
传奇开心果编程5 天前
【Jetpack Compose娓娓道来】第29课:状态容器与UI事件建模——把“散装状态”变成“可穷举的契约”
android·kotlin·移动开发·android jetpack·ui框架·开发范式·声明式ui
alexhilton9 天前
用Konsist与Lint强制执行设计系统契约
android·kotlin·android jetpack
传奇开心果编程10 天前
组合优先开发范式:从汉字造字原理到声明式UI组件组合思想
android·flutter·ios·swiftui·移动开发·harmonyos·android jetpack
JMchen11 天前
【Jetpack 内核实战 08 】:组件整合——DataStore + Room 构建离线优先架构
架构·kotlin·android jetpack
Android打工仔11 天前
Compose 中何时该用 padding,何时该用 offset
android·android jetpack
hai_android17 天前
Android 设备锁定:Kiosk Mode 实战
android·java·android jetpack
stevenzqzq17 天前
Android Jetpack Compose 列表开发指南:为什么你不需要再为底部 Item 写额外间距
android·android jetpack