StateFlow vs. SharedFlow:Kotlin协程的“状态”与“事件”分发

260 阅读3分钟

一句话总结

  • StateFlow“实时状态同步器” (永远记住最新状态,新人一来就告诉当前值)。
  • SharedFlow“事件广播电台” (可以发多条消息,新人是否听历史消息由你定)。

一、核心差异:数据类型与处理策略

StateFlowSharedFlow 都是 Kotlin 协程中的热流(Hot Flow) ,这意味着它们可以在没有订阅者时继续存在并发射数据。但它们的设计目标和行为模式截然不同。

StateFlowSharedFlow
设计目标状态管理。代表一个可变的、有状态的值。事件分发。代表一个可以发射多个独立事件的广播。
初始值必须有初始值。确保任何订阅者在订阅时都能立即获得一个状态。不需要初始值。从零开始,按需发射事件。
值处理去重。如果新值与当前值相同,则不会向订阅者发送更新。每次发射都处理。即使值相同,每次 emit 都会向所有订阅者发送。
订阅行为粘性。新订阅者立即获得当前最新状态。可配置粘性。通过 replay 参数,可以决定新订阅者是否能收到历史事件。

二、实践案例:状态与事件的典型应用

理解两者的核心区别,能帮助我们为不同的业务场景选择合适的工具。

1. StateFlow:管理UI状态

当我们需要一个持久化的、随时可查询的值时,StateFlow 是最佳选择。

  • 示例:登录状态

    • StateFlow 始终持有当前用户的登录状态(LoggedInLoggedOut)。
    • 当用户登录时,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 的内存占用则取决于其 replayextraBufferCapacity 参数。

四、结论:如何选择?

  • 选择 StateFlow:当你的需求是管理和同步一个持久化的、可查询的状态时。
  • 选择 SharedFlow:当你的需求是广播瞬时事件,并需要对事件的缓存和订阅行为进行细粒度控制时。