StateFlow 和 SharedFlow 是 Flow API,它们使流能够以最优方式发出状态更新并将值发射给多个消费者。
StateFlow
StateFlow 是一个状态持有者可观察流,它将当前和新的状态更新发射给其收集器。当前状态值也可以通过其 value 属性读取。要更新状态并将其发送到流中,请为 MutableStateFlow 类的 value 属性赋一个新值。
在 Android 中,StateFlow 非常适合需要维护可观察可变状态的类。
遵循 Kotlin 流中的示例,可以从 LatestNewsViewModel 中公开一个 StateFlow,以便 View 可以监听 UI 状态更新,并使屏幕状态能够在配置更改后得以保留。
class LatestNewsViewModel(
private val newsRepository: NewsRepository
) : ViewModel() {
// Backing property to avoid state updates from other classes
private val _uiState = MutableStateFlow(LatestNewsUiState.Success(emptyList()))
// The UI collects from this StateFlow to get its state updates
val uiState: StateFlow<LatestNewsUiState> = _uiState
init {
viewModelScope.launch {
newsRepository.favoriteLatestNews
// Update View with the latest favorite news
// Writes to the value property of MutableStateFlow,
// adding a new element to the flow and updating all
// of its collectors
.collect { favoriteNews ->
_uiState.value = LatestNewsUiState.Success(favoriteNews)
}
}
}
}
// Represents different states for the LatestNews screen
sealed class LatestNewsUiState {
data class Success(val news: List<ArticleHeadline>): LatestNewsUiState()
data class Error(val exception: Throwable): LatestNewsUiState()
}
负责更新 MutableStateFlow 的类是生产者,所有从 StateFlow 收集数据的类都是消费者。与使用 flow 构建器构建的冷流不同,StateFlow 是热的:从该流中收集数据不会触发任何生产者代码。StateFlow 始终处于活动状态并驻留在内存中,只有当不存在来自垃圾回收根(garbage collection root)的其他引用时,它才会被垃圾回收。
当新的消费者开始从流中收集数据时,它会收到流中的最后状态以及任何后续状态。您可以在其他可观察类(如 LiveData)中找到这种行为。
View 像监听任何其他流一样监听 StateFlow
class LatestNewsActivity : AppCompatActivity() {
private val latestNewsViewModel = // getViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
...
// Start a coroutine in the lifecycle scope
lifecycleScope.launch {
// repeatOnLifecycle launches the block in a new coroutine every time the
// lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
repeatOnLifecycle(Lifecycle.State.STARTED) {
// Trigger the flow and start listening for values.
// Note that this happens when lifecycle is STARTED and stops
// collecting when the lifecycle is STOPPED
latestNewsViewModel.uiState.collect { uiState ->
// New value received
when (uiState) {
is LatestNewsUiState.Success -> showFavoriteNews(uiState.news)
is LatestNewsUiState.Error -> showError(uiState.exception)
}
}
}
}
}
}
要将任何流转换为 StateFlow,请使用 stateIn 中间运算符。
StateFlow、Flow 和 LiveData
StateFlow 和 LiveData 有相似之处。两者都是可观察的数据持有者类,且在应用架构中使用时都遵循类似的模式。
但是请注意,StateFlow 和 LiveData 的行为确实有所不同
StateFlow要求在构造函数中传入初始状态,而LiveData则不需要。LiveData.observe()会在视图进入STOPPED状态时自动注销消费者,而从StateFlow或任何其他流中收集数据并不会自动停止收集。要实现相同的行为,您需要从Lifecycle.repeatOnLifecycle代码块中收集流。
使用 shareIn 将冷流变为热流
StateFlow 是一个热流——只要流被收集或者存在来自垃圾回收根的其他任何引用,它就会保留在内存中。您可以使用 shareIn 运算符将冷流转换为热流。
以 Kotlin 流中创建的 callbackFlow 为例,您不必让每个收集器都创建一个新流,而是可以使用 shareIn 在收集器之间共享从 Firestore 获取的数据。您需要传入以下内容
- 一个用于共享流的
CoroutineScope。该作用域的生命周期应长于任何消费者,以便在需要时保持共享流处于活动状态。 - 要重播给每个新收集器的项数。
- 启动行为策略。
class NewsRemoteDataSource(...,
private val externalScope: CoroutineScope,
) {
val latestNews: Flow<List<ArticleHeadline>> = flow {
...
}.shareIn(
externalScope,
replay = 1,
started = SharingStarted.WhileSubscribed()
)
}
在此示例中,latestNews 流会将最后发出的项重播给新收集器,并且只要 externalScope 处于活动状态且存在活动的收集器,它就会保持活动状态。SharingStarted.WhileSubscribed() 启动策略在有活动订阅者时保持上游生产者处于活动状态。还可以使用其他启动策略,例如 SharingStarted.Eagerly(立即启动生产者)或 SharingStarted.Lazily(在出现第一个订阅者后开始共享并永久保持流处于活动状态)。
SharedFlow
shareIn 函数返回一个 SharedFlow,这是一个向所有从中收集数据的消费者发出值的热流。SharedFlow 是 StateFlow 的一种高度可配置的泛化形式。
您可以在不使用 shareIn 的情况下创建 SharedFlow。例如,您可以使用 SharedFlow 向应用的其余部分发送滴答(tick),以便所有内容都能同时定期刷新。除了获取最新新闻外,您可能还希望刷新包含用户收藏主题集合的用户信息部分。在以下代码片段中,TickHandler 公开了 SharedFlow,以便其他类知道何时刷新其内容。与 StateFlow 一样,在类中使用类型为 MutableSharedFlow 的支持属性将项发送到流中
// Class that centralizes when the content of the app needs to be refreshed
class TickHandler(
private val externalScope: CoroutineScope,
private val tickIntervalMs: Long = 5000
) {
// Backing property to avoid flow emissions from other classes
private val _tickFlow = MutableSharedFlow<Unit>(replay = 0)
val tickFlow: SharedFlow<Unit> = _tickFlow
init {
externalScope.launch {
while(true) {
_tickFlow.emit(Unit)
delay(tickIntervalMs)
}
}
}
}
class NewsRepository(
...,
private val tickHandler: TickHandler,
private val externalScope: CoroutineScope
) {
init {
externalScope.launch {
// Listen for tick updates
tickHandler.tickFlow.collect {
refreshLatestNews()
}
}
}
suspend fun refreshLatestNews() { ... }
...
}
您可以通过以下方式自定义 SharedFlow 行为
replay允许您为新订阅者重新发送一定数量之前已发出的值。onBufferOverflow允许您指定当缓冲区已满且无法发送更多项时的策略。默认值为BufferOverflow.SUSPEND,这会使调用者挂起。其他选项包括DROP_LATEST或DROP_OLDEST。
MutableSharedFlow 还具有一个 subscriptionCount 属性,其中包含活跃收集器的数量,以便您可以据此优化业务逻辑。MutableSharedFlow 还包含一个 resetReplayCache 函数,如果您不想重播发送到流中的最新信息,可以使用它。
其他流资源
- Android 上的 Kotlin Flow
- 在 Android 上测试 Kotlin 流
- 关于 Flow 的 shareIn 和 stateIn 运算符需要了解的事项
- 从 LiveData 迁移到 Kotlin Flow
- 有关 Kotlin 协程和流的额外资源