kotlin 01flow-StateFlow 完整教程

一 Android StateFlow 完整教程:从入门到实战

StateFlow 是 Kotlin 协程库中用于状态管理的响应式流,特别适合在 Android 应用开发中管理 UI 状态。本教程将带全面了解 StateFlow 的使用方法。

1. StateFlow 基础概念

1.1 什么是 StateFlow?

StateFlow 是 Kotlin 协程提供的一种热流(Hot Flow),它具有以下特点:

  • 总是有当前值(初始值必须提供)
  • 只保留最新值
  • 支持多个观察者
  • 与 LiveData 类似但基于协程

1.2 StateFlow vs LiveData

特性 StateFlow LiveData
生命周期感知 否(需配合 lifecycleScope)
需要初始值
基于 协程 观察者模式
线程控制 通过 Dispatcher 主线程
背压处理 自动处理 自动处理

2. 基本使用

2.1 添加依赖

在 build.gradle 中添加:

kotlin 复制代码
dependencies {
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1"
}

2.2 创建 StateFlow

kotlin 复制代码
class MyViewModel : ViewModel() {
    // 私有可变的StateFlow
    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
    
    // 公开不可变的StateFlow
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()
    
    sealed class UiState {
        object Loading : UiState()
        data class Success(val data: String) : UiState()
        data class Error(val message: String) : UiState()
    }
    
    fun loadData() {
        viewModelScope.launch {
            _uiState.value = UiState.Loading
            try {
                val result = repository.fetchData()
                _uiState.value = UiState.Success(result)
            } catch (e: Exception) {
                _uiState.value = UiState.Error(e.message ?: "Unknown error")
            }
        }
    }
}

2.3 在 Activity/Fragment 中收集 StateFlow

kotlin 复制代码
class MyActivity : AppCompatActivity() {
    private val viewModel: MyViewModel by viewModels()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect { state ->
                    when (state) {
                        is MyViewModel.UiState.Loading -> showLoading()
                        is MyViewModel.UiState.Success -> showData(state.data)
                        is MyViewModel.UiState.Error -> showError(state.message)
                    }
                }
            }
        }
    }
    
    private fun showLoading() { /*...*/ }
    private fun showData(data: String) { /*...*/ }
    private fun showError(message: String) { /*...*/ }
}

3. 高级用法

3.1 结合 SharedFlow 处理一次性事件

kotlin 复制代码
class EventViewModel : ViewModel() {
    private val _events = MutableSharedFlow<Event>()
    val events = _events.asSharedFlow()
    
    sealed class Event {
        data class ShowToast(val message: String) : Event()
        object NavigateToNextScreen : Event()
    }
    
    fun triggerEvent() {
        viewModelScope.launch {
            _events.emit(Event.ShowToast("Hello World!"))
        }
    }
}

// 在Activity中收集
lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.events.collect { event ->
            when (event) {
                is EventViewModel.Event.ShowToast -> showToast(event.message)
                EventViewModel.Event.NavigateToNextScreen -> navigateToNext()
            }
        }
    }
}

3.2 状态合并 (combine)

kotlin 复制代码
val userName = MutableStateFlow("")
val userAge = MutableStateFlow(0)

val userInfo = combine(userName, userAge) { name, age ->
    "Name: $name, Age: $age"
}

// 收集合并后的流
userInfo.collect { info ->
    println(info)
}

3.3 状态转换 (map, filter, etc.)

kotlin 复制代码
val numbers = MutableStateFlow(0)

val evenNumbers = numbers
    .filter { it % 2 == 0 }
    .map { "Even: $it" }

evenNumbers.collect { println(it) }

4. 性能优化

4.1 使用 stateIn 缓存 StateFlow

kotlin 复制代码
val networkFlow = flow {
    // 模拟网络请求
    emit(repository.fetchData())
}

val cachedState = networkFlow.stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(5000), // 5秒无订阅者停止
    initialValue = "Loading..."
)

4.2 避免重复收集

kotlin 复制代码
// 错误方式 - 每次重组都会创建新的收集器
@Composable
fun MyComposable(viewModel: MyViewModel) {
    val state by viewModel.state.collectAsState()
    // ...
}

// 正确方式 - 使用 derivedStateOf 或 remember
@Composable
fun MyComposable(viewModel: MyViewModel) {
    val state by remember { viewModel.state }.collectAsState()
    // ...
}

5. 测试 StateFlow

5.1 单元测试

kotlin 复制代码
@Test
fun `test state flow`() = runTest {
    val viewModel = MyViewModel()
    
    val results = mutableListOf<MyViewModel.UiState>()
    val job = launch {
        viewModel.uiState.collect { results.add(it) }
    }
    
    viewModel.loadData()
    advanceUntilIdle()
    
    assertEquals(3, results.size) // Loading, Success/Error
    assertTrue(results[0] is MyViewModel.UiState.Loading)
    
    job.cancel()
}

5.2 使用 Turbine 测试库

kotlin 复制代码
dependencies {
    testImplementation "app.cash.turbine:turbine:0.12.1"
}

@Test
fun `test with turbine`() = runTest {
    val viewModel = MyViewModel()
    
    viewModel.uiState.test {
        viewModel.loadData()
        
        assertEquals(MyViewModel.UiState.Loading, awaitItem())
        val success = awaitItem()
        assertTrue(success is MyViewModel.UiState.Success)
        
        cancelAndIgnoreRemainingEvents()
    }
}

6. 常见问题解答

Q1: StateFlow 和 LiveData 哪个更好?

StateFlow 更适合协程环境,LiveData 更简单但功能较少。新项目推荐 StateFlow。

Q2: 如何处理背压(Backpressure)?

StateFlow 自动处理背压,只保留最新值。

Q3: 为什么我的收集器没有收到更新?

检查:

  1. 是否在正确的生命周期范围内收集
  2. Flow 是否有发射新值
  3. 是否在正确的协程上下文中

Q4: 如何避免内存泄漏?

使用 repeatOnLifecycleflowWithLifecycle 确保只在活跃生命周期收集。

7. 完整示例项目

以下是一个完整的 ViewModel 示例:

kotlin 复制代码
class UserViewModel(private val userRepository: UserRepository) : ViewModel() {
    private val _userState = MutableStateFlow<UserState>(UserState.Loading)
    val userState: StateFlow<UserState> = _userState.asStateFlow()
    
    private val _events = MutableSharedFlow<UserEvent>()
    val events: SharedFlow<UserEvent> = _events.asSharedFlow()
    
    init {
        loadUser()
    }
    
    fun loadUser() {
        viewModelScope.launch {
            _userState.value = UserState.Loading
            try {
                val user = userRepository.getUser()
                _userState.value = UserState.Success(user)
            } catch (e: Exception) {
                _userState.value = UserState.Error(e.message ?: "Unknown error")
                _events.emit(UserEvent.ShowErrorToast("Failed to load user"))
            }
        }
    }
    
    fun updateUserName(name: String) {
        viewModelScope.launch {
            val currentUser = (_userState.value as? UserState.Success)?.user ?: return@launch
            val updatedUser = currentUser.copy(name = name)
            _userState.value = UserState.Success(updatedUser)
            userRepository.updateUser(updatedUser)
        }
    }
    
    sealed class UserState {
        object Loading : UserState()
        data class Success(val user: User) : UserState()
        data class Error(val message: String) : UserState()
    }
    
    sealed class UserEvent {
        data class ShowErrorToast(val message: String) : UserEvent()
    }
}

通过本教程,应该已经掌握了 StateFlow 的核心用法。StateFlow 是构建响应式 Android 应用的强大工具,结合协程可以提供更简洁、更安全的状态管理方案。

二 stateFlow和sharedFlow的区别

关于 StateFlowSharedFlow 的问题非常关键。它们都是 Kotlin Flow 的"热流"(hot stream)变种,广泛用于 事件驱动编程状态管理。下面逐条详细解答问题:


✅ 1. StateFlow 和 SharedFlow 的区别?能否互相转换?

特性 StateFlow SharedFlow
是否需要初始值 ✅ 是(必须有) ❌ 否(可选)
是否有当前值 .value ✅ 是 ❌ 没有
是否支持粘性订阅(可立即获取最新值) ✅ 是(始终有当前值) ❌ 否(默认非粘性,取决于 replay)
用于什么 状态管理(UI 状态) 一次性事件(导航、吐司等)
是否自动重发 ✅ 是:新订阅者立即拿到当前值 ❌ 否(需设置 replay > 0 才能重发)
是否支持多次发送相同值 ❌ 否(相同值不会触发 collect) ✅ 是
是否丢失事件 ❌ 不会丢(始终保留一个最新值) ✅ 可能丢(如果没有设置 replay 或 buffer)

🔁 能否互相转换?

不能直接转换,因为它们底层设计不同。但可以人为地模拟:

kotlin 复制代码
val sharedFlow = MutableSharedFlow<Int>(replay = 1)
val stateFlow = sharedFlow.stateIn(scope, SharingStarted.Eagerly, initialValue)

或者从 StateFlow 获取 SharedFlow 的行为:

kotlin 复制代码
val stateFlow: StateFlow<Int> = MutableStateFlow(0)
val sharedFlow = stateFlow.shareIn(scope, SharingStarted.Lazily)

✅ 2. 是不是 StateFlow 就是 SharedFlow 多了 .value 和初始值?

可以这么说,但这只是表面。它们的语义不同:

  • StateFlow 表示的是状态的容器(如 ViewModel 中的 UI 状态)。
  • SharedFlow 更像是事件的通道,没有"当前状态"的概念。
kotlin 复制代码
// StateFlow 的特点
println(stateFlow.value) // 始终可以拿到当前值

// SharedFlow 没有 value,只能 collect
sharedFlow.collect { event -> ... }

✅ 3. 如何处理"被压的"问题(Backpressure、丢失事件)?

📌 StateFlow

  • 没有丢失问题:永远保留最后一个值。
  • 新订阅者会立刻收到当前值。
  • 不支持 backpressure,因为没有队列:只有一个值。
kotlin 复制代码
val sf = MutableStateFlow(0)
sf.value = 1
sf.value = 2
// collect 会立即收到 2(中间的1不会被保留)

📌 SharedFlow

  • 默认不保留任何事件replay = 0,无 buffer)。
  • 如果事件很快发送但没人 collect,就直接丢失
  • 可以设置 replayextraBufferCapacity 来控制:
kotlin 复制代码
val shared = MutableSharedFlow<Int>(
    replay = 2,
    extraBufferCapacity = 5
)
  • 设置 onBufferOverflow = BufferOverflow.DROP_OLDEST/ DROP_LATEST / SUSPEND 控制丢弃策略。

✅ 使用建议总结

需求 推荐使用
需要保存并暴露当前状态(如 UI 状态) StateFlow
需要发送一次性事件,如 Toast、导航 SharedFlow
想在订阅时立即拿到最后值 StateFlow
想发送多个相同值 SharedFlow(StateFlow 会去重)

三 MutableStateFlow 是如何更新值的?

_seatLevel.value = newValue_seatLevel.update { ... } 都可以用来更新 MutableStateFlow 的值,但它们在 并发安全性使用场景 上有区别:


1. _seatLevel.value = newValue(直接赋值)

  • 作用 :直接设置 MutableStateFlow 的新值。

  • 特点

    • 非原子操作 :如果在多线程环境下,直接赋值可能会导致竞态条件(Race Condition),因为 value 的读写不是线程安全的。
    • 简单直接:适用于单线程或确定不会并发更新的场景。
  • 示例

    kotlin 复制代码
    _seatLevel.value = 5 // 直接设置新值

2. _seatLevel.update { ... }(原子更新)

  • 作用 :以 原子方式 计算并更新 MutableStateFlow 的值。

  • 特点

    • 原子操作update 是线程安全的,内部使用 CAS (Compare-And-Swap) 机制,确保在并发环境下不会出现数据竞争。
    • 基于当前值计算 :可以访问当前值(current),并返回新值。
    • 适合条件更新:适用于需要依赖当前值进行计算的场景(如递增、条件过滤等)。
  • 示例

    kotlin 复制代码
    _seatLevel.update { current ->
        if (newLevel in 0..100) newLevel else current
    }

    这里:

    • current 是当前 _seatLevel 的值。
    • 返回的值会作为新值(如果 newLevel 不在 0..100 范围内,则保持原值)。

关键区别

特性 _seatLevel.value = newValue _seatLevel.update { ... }
线程安全 ❌ 非原子操作,可能竞态条件 ✅ 原子操作,线程安全
是否依赖当前值 ❌ 直接赋值,不关心当前值 ✅ 可以访问当前值并计算新值
适用场景 单线程或确定无并发更新 多线程环境或需要条件更新

如何选择?

  1. 如果只是简单赋值,且确定不会并发更新 (例如 UI 线程中),可以直接用 value = newValue
  2. 如果涉及多线程,或需要基于当前值计算新值 (如递增、条件过滤等),应该用 update { ... }

底层实现

  • update 内部使用了 while (true) + compareAndSet 机制,确保原子性:

    kotlin 复制代码
    public inline fun update(function: (T) -> T) {
        while (true) {
            val prevValue = value
            val nextValue = function(prevValue)
            if (compareAndSet(prevValue, nextValue)) {
                return
            }
        }
    }

    它会不断尝试更新,直到成功(避免并发冲突)。


总结

  • value = newValue:简单直接,但不适合并发场景。
  • update { ... }:线程安全,适合多线程或需要依赖当前值的更新。

在你的 updateLevel 函数中,由于涉及条件判断(newLevel in 0..100),使用 update 是更安全、更合理的选择。

四 为什么update 更新是原子性?

相关代码

复制代码
public inline fun update(function: (T) -> T) {
    while (true) {
        val prevValue = value
        val nextValue = function(prevValue)
        if (compareAndSet(prevValue, nextValue)) {
            return
        }
    }
} 

这段代码之所以能保证 MutableStateFlow 的更新是 原子性(Atomic) 的,关键在于它使用了 CAS(Compare-And-Swap) 机制,并通过 while (true) 循环确保最终成功更新。我们逐行分析其原子性保证:


1. compareAndSet(prevValue, nextValue) 是关键

compareAndSetAtomic 操作(底层基于 CPU 的 CAS 指令),它的行为是:

kotlin 复制代码
fun compareAndSet(expect: T, newValue: T): Boolean
  • 如果当前 value == expect (说明其他线程尚未修改),则更新为 newValue 并返回 true
  • 如果当前 value != expect (说明其他线程已经修改),则放弃更新并返回 false

由于 compareAndSet硬件级原子操作,它保证了:

  • 读取 value比较 expect写入 newValue 这三个步骤是 不可分割的(不会被其他线程打断)。

2. while (true) 循环的作用

如果 compareAndSet 失败(返回 false),说明其他线程已经修改了 value,此时:

  1. 重新读取最新的 valueval prevValue = value)。
  2. 重新计算 nextValuefunction(prevValue))。
  3. 再次尝试 compareAndSet,直到成功为止。

这种 乐观锁(Optimistic Locking) 策略确保了:

  • 即使多线程竞争,最终所有更新都会 按顺序应用(不会丢失任何修改)。
  • 不会发生 死锁(因为没有阻塞,只是重试)。

3. 为什么能保证原子性?

  • compareAndSet 是原子的 :单次 compareAndSet 调用是线程安全的。
  • 循环直到成功:即使其他线程并发修改,当前线程最终会基于最新的值成功更新。
  • 函数式更新function(prevValue) 的计算是基于最新的 prevValue,不会出现脏数据。

4. 对比非原子更新的问题

如果直接用 value = newValue

kotlin 复制代码
// 非原子操作,可能导致竞态条件
_seatLevel.value = _seatLevel.value + 1

在多线程环境下:

  1. 线程 A 读取 value = 0
  2. 线程 B 读取 value = 0
  3. 线程 A 写入 value = 1
  4. 线程 B 写入 value = 1(本应是 2,但结果错误!)。

update 能避免这个问题:

kotlin 复制代码
_seatLevel.update { current -> current + 1 } // 线程安全

5. 原子性的直观体现

从代码中可以看出原子性的关键点:

  1. val prevValue = value:读取当前值(可能被其他线程修改)。
  2. val nextValue = function(prevValue):基于当前值计算新值。
  3. if (compareAndSet(prevValue, nextValue))
    • 只有 value 仍等于 prevValue 时才会更新。
    • 如果失败(说明其他线程已修改),则 重试,确保最终更新是基于最新值。

6. 类似机制的对比

  • Java 的 AtomicInteger

    java 复制代码
    atomicInt.updateAndGet(x -> x + 1); // 同样基于 CAS
  • 数据库的乐观锁

    sql 复制代码
    UPDATE table SET value = newValue WHERE value = oldValue; -- 类似 CAS

总结

这段代码的原子性由以下两点保证:

  1. compareAndSet 的原子性:确保比较和更新的操作不可分割。
  2. 循环重试机制:确保并发冲突时最终能成功更新。

因此,update { ... } 是线程安全的,适合在多线程环境下使用,而直接 value = newValue 不是。

相关推荐
那我掉的头发算什么3 分钟前
【数据库】navicat的下载以及数据库约束
android·数据库·数据仓库·sql·mysql·数据库开发·数据库架构
csbysj20207 分钟前
Scala 字符串
开发语言
自动化小秋葵35 分钟前
Python入门经典题目
开发语言·python
明道源码1 小时前
Android Studio 应用运行到真机设备
android·ide·android studio
生莫甲鲁浪戴1 小时前
Android Studio新手开发第二十五天
android·ide·android studio
Varpb1 小时前
android studio-设置android模拟器屏幕自动旋转
android·android studio
编程岁月1 小时前
java面试-0305-java线程调度方法?sleep()和wait()区别?
java·开发语言·面试
凌晨一点的秃头猪1 小时前
Python 常见 bug 总结和异常处理
开发语言·python·bug
云知谷1 小时前
【经典书籍】C++ Primer 第19章特殊工具与技术精华讲解
c语言·开发语言·c++·软件工程·团队开发
liu****2 小时前
4.基础开发工具(一)
linux·开发语言·1024程序员节