一句话总结
StateFlow→ “实时状态同步器” (永远记住最新状态,新人一来就告诉当前值)。SharedFlow→ “事件广播电台” (可以发多条消息,新人是否听历史消息由你定)。
一、核心差异:数据类型与处理策略
StateFlow 和 SharedFlow 都是 Kotlin 协程中的热流(Hot Flow) ,这意味着它们可以在没有订阅者时继续存在并发射数据。但它们的设计目标和行为模式截然不同。
StateFlow | SharedFlow | |
|---|---|---|
| 设计目标 | 状态管理。代表一个可变的、有状态的值。 | 事件分发。代表一个可以发射多个独立事件的广播。 |
| 初始值 | 必须有初始值。确保任何订阅者在订阅时都能立即获得一个状态。 | 不需要初始值。从零开始,按需发射事件。 |
| 值处理 | 去重。如果新值与当前值相同,则不会向订阅者发送更新。 | 每次发射都处理。即使值相同,每次 emit 都会向所有订阅者发送。 |
| 订阅行为 | 粘性。新订阅者立即获得当前最新状态。 | 可配置粘性。通过 replay 参数,可以决定新订阅者是否能收到历史事件。 |
二、实践案例:状态与事件的典型应用
理解两者的核心区别,能帮助我们为不同的业务场景选择合适的工具。
1. StateFlow:管理UI状态
当我们需要一个持久化的、随时可查询的值时,StateFlow 是最佳选择。
-
示例:登录状态
StateFlow始终持有当前用户的登录状态(LoggedIn或LoggedOut)。- 当用户登录时,
stateFlow.value = LoggedIn。任何新的订阅者(如进入主页的Fragment)都会立即收到LoggedIn状态,并据此更新UI。
// ViewModel中的登录状态
private val _loginState = MutableStateFlow<LoginState>(LoginState.LoggedOut)
val loginState: StateFlow<LoginState> = _loginState
fun login() {
// 异步登录
viewModelScope.launch {
// ... 登录成功
_loginState.value = LoginState.LoggedIn // 更新状态
}
}
2. SharedFlow:处理瞬时事件
当我们需要处理那些“一次性”的、不应重复处理的事件时,SharedFlow 是首选。
-
示例:导航事件
- 当用户点击一个按钮需要跳转到新页面时,这是一个瞬时事件。我们不希望在配置变更(如屏幕旋转)后,导航事件再次被触发。
SharedFlow的默认replay = 0行为,确保事件只会被当前订阅者消费,不会在后续被重放。
// ViewModel中的导航事件
private val _navigationEvents = MutableSharedFlow<NavigationEvent>()
val navigationEvents = _navigationEvents.asSharedFlow()
fun onNavigateToDetail() {
viewModelScope.launch {
_navigationEvents.emit(NavigationEvent.ToDetail) // 发送导航事件
}
}
三、高级用法与考量
- 背压(Backpressure) :
StateFlow默认处理背压,它只保留最新状态。而SharedFlow则通过extraBufferCapacity参数来控制在没有订阅者时可以缓存多少事件,以应对事件峰值。 conflate策略:如果一个SharedFlow的生产者发射速度远快于其订阅者消费速度,可以应用conflate操作符,使订阅者只接收到最新值,从而避免处理过时的中间事件。- 性能与内存:
StateFlow的内存占用固定,因为它只存储最新值。SharedFlow的内存占用则取决于其replay和extraBufferCapacity参数。
四、结论:如何选择?
- 选择
StateFlow:当你的需求是管理和同步一个持久化的、可查询的状态时。 - 选择
SharedFlow:当你的需求是广播瞬时事件,并需要对事件的缓存和订阅行为进行细粒度控制时。