一 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 中添加:
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
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
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 处理一次性事件
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)
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.)
val numbers = MutableStateFlow(0)
val evenNumbers = numbers
.filter { it % 2 == 0 }
.map { "Even: $it" }
evenNumbers.collect { println(it) }
4. 性能优化
4.1 使用 stateIn 缓存 StateFlow
val networkFlow = flow {
// 模拟网络请求
emit(repository.fetchData())
}
val cachedState = networkFlow.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000), // 5秒无订阅者停止
initialValue = "Loading..."
)
4.2 避免重复收集
// 错误方式 - 每次重组都会创建新的收集器
@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 单元测试
@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 测试库
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: 为什么我的收集器没有收到更新?
检查:
- 是否在正确的生命周期范围内收集
- Flow 是否有发射新值
- 是否在正确的协程上下文中
Q4: 如何避免内存泄漏?
使用 repeatOnLifecycle
或 flowWithLifecycle
确保只在活跃生命周期收集。
7. 完整示例项目
以下是一个完整的 ViewModel 示例:
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的区别
关于 StateFlow
和 SharedFlow
的问题非常关键。它们都是 Kotlin Flow
的“热流”(hot stream)变种,广泛用于 事件驱动编程 和 状态管理。下面逐条详细解答问题:
✅ 1. StateFlow 和 SharedFlow 的区别?能否互相转换?
特性 | StateFlow | SharedFlow |
---|---|---|
是否需要初始值 | ✅ 是(必须有) | ❌ 否(可选) |
是否有当前值 .value | ✅ 是 | ❌ 没有 |
是否支持粘性订阅(可立即获取最新值) | ✅ 是(始终有当前值) | ❌ 否(默认非粘性,取决于 replay) |
用于什么 | 状态管理(UI 状态) | 一次性事件(导航、吐司等) |
是否自动重发 | ✅ 是:新订阅者立即拿到当前值 | ❌ 否(需设置 replay > 0 才能重发) |
是否支持多次发送相同值 | ❌ 否(相同值不会触发 collect) | ✅ 是 |
是否丢失事件 | ❌ 不会丢(始终保留一个最新值) | ✅ 可能丢(如果没有设置 replay 或 buffer) |
🔁 能否互相转换?
不能直接转换,因为它们底层设计不同。但可以人为地模拟:
val sharedFlow = MutableSharedFlow<Int>(replay = 1)
val stateFlow = sharedFlow.stateIn(scope, SharingStarted.Eagerly, initialValue)
或者从 StateFlow
获取 SharedFlow
的行为:
val stateFlow: StateFlow<Int> = MutableStateFlow(0)
val sharedFlow = stateFlow.shareIn(scope, SharingStarted.Lazily)
✅ 2. 是不是 StateFlow 就是 SharedFlow 多了 .value
和初始值?
可以这么说,但这只是表面。它们的语义不同:
StateFlow
表示的是状态的容器(如 ViewModel 中的 UI 状态)。SharedFlow
更像是事件的通道,没有“当前状态”的概念。
// StateFlow 的特点
println(stateFlow.value) // 始终可以拿到当前值
// SharedFlow 没有 value,只能 collect
sharedFlow.collect { event -> ... }
✅ 3. 如何处理“被压的”问题(Backpressure、丢失事件)?
📌 StateFlow
:
- 没有丢失问题:永远保留最后一个值。
- 新订阅者会立刻收到当前值。
- 不支持 backpressure,因为没有队列:只有一个值。
val sf = MutableStateFlow(0)
sf.value = 1
sf.value = 2
// collect 会立即收到 2(中间的1不会被保留)
📌 SharedFlow
:
- 默认不保留任何事件(
replay = 0
,无 buffer)。 - 如果事件很快发送但没人 collect,就直接丢失。
- 可以设置
replay
和extraBufferCapacity
来控制:
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
的读写不是线程安全的。 - 简单直接:适用于单线程或确定不会并发更新的场景。
- 非原子操作:如果在多线程环境下,直接赋值可能会导致竞态条件(Race Condition),因为
- 示例:
_seatLevel.value = 5 // 直接设置新值
2. _seatLevel.update { ... }
(原子更新)
- 作用:以 原子方式 计算并更新
MutableStateFlow
的值。 - 特点:
- 原子操作:
update
是线程安全的,内部使用CAS (Compare-And-Swap)
机制,确保在并发环境下不会出现数据竞争。 - 基于当前值计算:可以访问当前值(
current
),并返回新值。 - 适合条件更新:适用于需要依赖当前值进行计算的场景(如递增、条件过滤等)。
- 原子操作:
- 示例:
这里:_seatLevel.update { current -> if (newLevel in 0..100) newLevel else current }
current
是当前_seatLevel
的值。- 返回的值会作为新值(如果
newLevel
不在0..100
范围内,则保持原值)。
关键区别
特性 | _seatLevel.value = newValue | _seatLevel.update { ... } |
---|---|---|
线程安全 | ❌ 非原子操作,可能竞态条件 | ✅ 原子操作,线程安全 |
是否依赖当前值 | ❌ 直接赋值,不关心当前值 | ✅ 可以访问当前值并计算新值 |
适用场景 | 单线程或确定无并发更新 | 多线程环境或需要条件更新 |
如何选择?
- 如果只是简单赋值,且确定不会并发更新(例如 UI 线程中),可以直接用
value = newValue
。 - 如果涉及多线程,或需要基于当前值计算新值(如递增、条件过滤等),应该用
update { ... }
。
底层实现
update
内部使用了while (true)
+compareAndSet
机制,确保原子性:
它会不断尝试更新,直到成功(避免并发冲突)。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)
是关键
compareAndSet
是 Atomic
操作(底层基于 CPU 的 CAS 指令),它的行为是:
fun compareAndSet(expect: T, newValue: T): Boolean
- 如果当前
value == expect
(说明其他线程尚未修改),则更新为newValue
并返回true
。 - 如果当前
value != expect
(说明其他线程已经修改),则放弃更新并返回false
。
由于 compareAndSet
是 硬件级原子操作,它保证了:
- 读取
value
、比较expect
和 写入newValue
这三个步骤是 不可分割的(不会被其他线程打断)。
2. while (true)
循环的作用
如果 compareAndSet
失败(返回 false
),说明其他线程已经修改了 value
,此时:
- 重新读取最新的
value
(val prevValue = value
)。 - 重新计算
nextValue
(function(prevValue)
)。 - 再次尝试
compareAndSet
,直到成功为止。
这种 乐观锁(Optimistic Locking) 策略确保了:
- 即使多线程竞争,最终所有更新都会 按顺序应用(不会丢失任何修改)。
- 不会发生 死锁(因为没有阻塞,只是重试)。
3. 为什么能保证原子性?
compareAndSet
是原子的:单次compareAndSet
调用是线程安全的。- 循环直到成功:即使其他线程并发修改,当前线程最终会基于最新的值成功更新。
- 函数式更新:
function(prevValue)
的计算是基于最新的prevValue
,不会出现脏数据。
4. 对比非原子更新的问题
如果直接用 value = newValue
:
// 非原子操作,可能导致竞态条件
_seatLevel.value = _seatLevel.value + 1
在多线程环境下:
- 线程 A 读取
value = 0
。 - 线程 B 读取
value = 0
。 - 线程 A 写入
value = 1
。 - 线程 B 写入
value = 1
(本应是 2,但结果错误!)。
而 update
能避免这个问题:
_seatLevel.update { current -> current + 1 } // 线程安全
5. 原子性的直观体现
从代码中可以看出原子性的关键点:
val prevValue = value
:读取当前值(可能被其他线程修改)。val nextValue = function(prevValue)
:基于当前值计算新值。if (compareAndSet(prevValue, nextValue))
:- 只有
value
仍等于prevValue
时才会更新。 - 如果失败(说明其他线程已修改),则 重试,确保最终更新是基于最新值。
- 只有
6. 类似机制的对比
- Java 的
AtomicInteger
:atomicInt.updateAndGet(x -> x + 1); // 同样基于 CAS
- 数据库的乐观锁:
UPDATE table SET value = newValue WHERE value = oldValue; -- 类似 CAS
总结
这段代码的原子性由以下两点保证:
compareAndSet
的原子性:确保比较和更新的操作不可分割。- 循环重试机制:确保并发冲突时最终能成功更新。
因此,update { ... }
是线程安全的,适合在多线程环境下使用,而直接 value = newValue
不是。