StateFlow 中update的用法

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

一、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")
    }
}
相关推荐
杉氧17 小时前
Framework 补完计划 (2):BufferQueue 与 SurfaceFlinger —— 像素的跨进程“接力赛”
android·架构·android jetpack
我命由我123451 天前
Android 开发问题:ClickableSpan 的点击事件没有生效
java·java-ee·android studio·android jetpack·android-studio·android runtime
我命由我123452 天前
Android 构建项目问题:XML 片段出错,com.ctc.wstx.exc.WstxUnexpectedCharException
android·xml·android studio·android jetpack·android-studio·android runtime
杉氧4 天前
跨平台资源管理:一套代码如何搞定 Android、iOS 和 Web 的图片与多语言?
android·架构·android jetpack
杉氧5 天前
兼容与共生:如何在旧项目中优雅地引入 Compose?
android·架构·android jetpack
杉氧5 天前
性能优化实战:如何定位冗余重组并榨干 Compose 的每一帧性能?
android·架构·android jetpack
alexhilton5 天前
将应用迁移到Navigation 3:痛点、加班和紧急修复
android·kotlin·android jetpack
杉氧5 天前
Navigation Compose 深度实践:如何优雅地串联起你的全栈 App?
android·架构·android jetpack
黄林晴6 天前
Room 3.0 正式发布!包名彻底重构,KMP 成为核心主线
android·android jetpack
杉氧7 天前
Compose 时代的 MVI 架构:如何用单向数据流驱动复杂 UI?
android·架构·android jetpack