在 Kotlin 协程生态中,StateFlow
和 SharedFlow
是用于处理数据流的核心组件,分别针对状态管理 和事件分发 场景设计,都是 Kotlin 协程库中的热流(Hot Flow)实现,但它们的设计目的和行为特性有显著不同,以下从核心区别、使用场景以及示例代码进行详细解析:
一、核心区别
特性 | StateFlow | SharedFlow |
---|---|---|
设计目的 | 管理应用状态(如 UI 状态) | 分发一次性事件(如用户操作、通知) |
初始值要求 | 必须提供初始值 (initialValue ) |
无需初始值 |
数据缓存策略 | 仅保留最新值(类似 LiveData ) |
可配置缓存历史数据(replay + buffer ) |
重复值处理 | 自动去重(连续相同值不会触发更新) | 保留所有值(需手动去重) |
背压处理 | 无显式缓冲,直接覆盖旧值 | 支持缓冲区和溢出策略(如丢弃旧值或挂起生产者) |
订阅者行为 | 新订阅者立即收到最新值 | 默认无缓存时,新订阅者仅收到订阅后的数据 |
二、使用场景
1. StateFlow:状态管理
-
适用场景:
- UI 状态:页面加载状态、用户登录状态、表单数据。
- 持久化数据:需要跨组件共享的实时数据(如网络请求结果)。
-
优势:
- 自动确保数据一致性,避免状态冲突。
- 与 Jetpack Compose 或 View 系统无缝集成。
2. SharedFlow:事件分发
-
适用场景:
- 一次性事件:Toast 提示、导航请求、权限结果。
- 多订阅者广播:多个组件监听同一事件流(如埋点上报)。
-
优势:
- 灵活配置历史数据重播(
replay
)。 - 支持处理高频率事件(如传感器数据流)。
- 灵活配置历史数据重播(
三、示例代码
1. StateFlow 示例:管理用户登录状态
kotlin
// ViewModel 中定义状态
class UserViewModel : ViewModel() {
private val _userState = MutableStateFlow<User?>(null) // 初始值为 null
val userState: StateFlow<User?> = _userState.asStateFlow()
fun login(username: String, password: String) {
viewModelScope.launch {
_userState.value = User.Loading
val result = repository.login(username, password)
_userState.value = result.fold(
onSuccess = { User.LoggedIn(it) },
onFailure = { User.Error(it.message) }
)
}
}
}
// Activity/Fragment 中监听状态
lifecycleScope.launch {
viewModel.userState.collect { state ->
when (state) {
is User.Loading -> showProgressBar()
is User.LoggedIn -> updateUI(state.data)
is User.Error -> showError(state.message)
null -> Unit // 初始状态处理
}
}
}
2. SharedFlow 示例:发送 Toast 通知
kotlin
// ViewModel 中定义事件流
class EventViewModel : ViewModel() {
private val _toastEvents = MutableSharedFlow<String>()
val toastEvents: SharedFlow<String> = _toastEvents.asSharedFlow()
fun showToast(message: String) {
viewModelScope.launch {
_toastEvents.emit(message) // 发送事件
}
}
}
// Activity/Fragment 中收集事件
lifecycleScope.launch {
viewModel.toastEvents.collect { message ->
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
}
3. SharedFlow 高级配置:缓存历史事件
kotlin
// 配置 replay=1 表示新订阅者收到最近一次事件
private val _events = MutableSharedFlow<Event>(
replay = 1,
extraBufferCapacity = 10, // 缓冲区总容量 = replay + extraBufferCapacity
onBufferOverflow = BufferOverflow.DROP_OLDEST // 缓冲区满时丢弃旧事件
)
// 发送事件
fun sendEvent(event: Event) {
viewModelScope.launch {
_events.emit(event)
}
}
四、选择策略
场景 | 推荐使用 | 理由 |
---|---|---|
需要持久化最新状态(如 UI 数据) | StateFlow | 自动处理状态更新,确保界面始终反映最新数据。 |
分发一次性事件(如错误提示) | SharedFlow | 避免状态残留,事件仅处理一次。 |
多订阅者共享高频事件(如埋点) | SharedFlow | 通过 replay 和缓冲区配置,支持多个消费者独立处理事件流。 |
需要与 Jetpack Compose 集成 | StateFlow | Compose 的 collectAsState() 天然支持 StateFlow。 |
五、最佳实践
1. 避免在 StateFlow 中存储事件:
StateFlow 的自动去重特性可能导致事件丢失。应使用 SharedFlow 处理事件。
2. 生命周期感知收集:
使用 lifecycleScope.launch
或 repeatOnLifecycle
避免界面不可见时的资源浪费。
kotlin
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.userState.collect { updateUI(it) }
}
}
3. SharedFlow 的防抖处理:
对高频事件(如输入框文本变化)使用 debounce
操作符。
kotlin
val searchQuery = MutableSharedFlow<String>()
searchQuery
.debounce(300) // 300ms 防抖
.distinctUntilChanged()
.collect { query -> search(query) }
4. 单元测试:
使用 turbine
库测试流行为。
kotlin
@Test
fun testUserState() = runTest {
viewModel.userState.test {
assertThat(awaitItem()).isNull() // 初始值
viewModel.login("user", "pass")
assertThat(awaitItem()).isInstanceOf<User.Loading>()
val result = awaitItem()
assertThat(result).isInstanceOf<User.LoggedIn>()
}
}
六、性能与内存优化
1. StateFlow 轻量化:
确保 StateFlow
持有的状态对象不可变且轻量,避免在状态中存储大型数据(如 Bitmap)。
2. SharedFlow 缓冲区控制:
合理设置 replay
和 extraBufferCapacity
,避免内存泄漏。例如:
kotlin
// 最多缓存 5 个事件,溢出时丢弃旧事件
MutableSharedFlow<Event>(replay = 0, extraBufferCapacity = 5)
3. 冷流转换:
将冷流(如 flow{}
)转换为 SharedFlow 时,使用 shareIn
并指定作用域。
kotlin
val dataFlow = repository.fetchData()
.shareIn(viewModelScope, SharingStarted.WhileSubscribed(5000))
七、常见问题
1. 为什么 StateFlow 有时不更新?
- 检查是否发送了相同的值(StateFlow 会去重)。
- 确保在协程中调用
value
的更新(如viewModelScope.launch
)。
2. SharedFlow 的事件丢失怎么办?
- 增加
replay
值或缓冲区大小。 - 使用
tryEmit
处理非挂起环境中的发送:
kotlin
if (!_events.tryEmit(event)) {
// 处理缓冲区满的情况
}
3. 如何同时使用 StateFlow 和 SharedFlow?
- 在 ViewModel 中分别定义状态和事件流:
kotlin
class MyViewModel : ViewModel() {
// 状态
val uiState: StateFlow<State> = ...
// 事件
val events: SharedFlow<Event> = ...
}
通过合理运用 StateFlow
和 SharedFlow
,可高效管理应用状态与事件流,提升代码可维护性和性能。
八、总结选择流程图
ini
需要管理状态吗?
├─ 是 → 需要初始值吗?
│ ├─ 是 → 使用 StateFlow
│ └─ 否 → 考虑使用可空类型的 StateFlow 或 SharedFlow
└─ 否 → 处理的是事件吗?
├─ 是 → 需要历史事件吗?
│ ├─ 是 → 使用 SharedFlow 配置适当 replay
│ └─ 否 → 使用 SharedFlow (replay=0)
└─ 否 → 可能需要重新评估需求