是什么
热流 指:无需收集者也能持续产出/保持数据 ,新的订阅者加入时可立即拿到最近值 或最近 N 条 。对比"冷流(collect 才启动)",热流更像广播 或可观察状态。
两大主角
1) StateFlow
-
语义:状态容器,"永远有一个当前值"。
-
特点:必须有初始值 ;始终只保留最新 ;收集时立即发一次当前值(粘性)。
-
适合:UI 状态(MVI/MVVM 的 UiState)。
常用写法:
kotlin
data class UiState(val items: List<Item> = emptyList(), val loading: Boolean = false)
class VM : ViewModel() {
private val _ui = MutableStateFlow(UiState())
val ui: StateFlow<UiState> = _ui
fun load() = viewModelScope.launch {
_ui.update { it.copy(loading = true) }
val data = repo.fetch()
_ui.update { UiState(items = data, loading = false) }
}
}
2) SharedFlow
-
语义:共享事件流 ,可配置重放条数 与缓冲。
-
特点:replay=0 默认非粘性 (新订阅者不补发历史);replay>0 粘性("Sticky")。
-
适合:一次性事件(导航、Toast)、或需要最近 N 条"回放"的通知。
常用写法:
kotlin
class VM : ViewModel() {
private val _events = MutableSharedFlow<UiEvent>(
replay = 0, // 非粘性事件
extraBufferCapacity = 64 // 下游慢时先缓一下
)
val events: SharedFlow<UiEvent> = _events
suspend fun toast(msg: String) {
_events.emit(UiEvent.Toast(msg)) // 挂起发送
// 或 _events.tryEmit(...) 非挂起发送
}
}
冷 → 热:stateIn/shareIn
把一次性计算/请求(冷流)升温为热流,解决"多处收集重复执行"的问题。
scss
// 冷流:搜索 + 防抖 + 只取最新请求
val resultCold: Flow<List<Item>> =
queryFlow
.debounce(300)
.distinctUntilChanged()
.flatMapLatest { q -> flow { emit(api.search(q)) }.flowOn(Dispatchers.IO) }
// 升温为 StateFlow(有初始值,适合 UI 展示)
val resultState: StateFlow<List<Item>> =
resultCold.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000), // 无订阅 5s 后取消上游
initialValue = emptyList()
)
// 或升温为 SharedFlow(广播式,配置 replay)
val resultShared: SharedFlow<List<Item>> =
resultCold.shareIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
replay = 1 // 新订阅者能立刻拿到最近一次结果
)
SharingStarted 选择:
-
Eagerly:立即启动上游;
-
WhileSubscribed(timeout):有订阅才跑,无订阅延迟停止(推荐);
-
Lazily:首个订阅者到来时启动。
使用要点(Android/Compose)
- 收集:Fragment 用 repeatOnLifecycle;Compose 用 collectAsStateWithLifecycle()
scss
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.ui.collect { render(it) }
}
}
-
背压:事件流可能"淤积",用 extraBufferCapacity、buffer()、conflate()、或 collectLatest()。
-
线程:上游 flowOn(Dispatchers.IO);UI 收集在 Main。
-
单次事件:SharedFlow(replay=0) 替代 SingleLiveEvent;若需"进来就看到上一次",设 replay=1。
典型场景速配
需求 | 推荐 |
---|---|
UI 状态(永远有值) | MutableStateFlow |
Toast/导航/一次性事件 | MutableSharedFlow(replay=0) |
需要"最后一次事件粘住" | MutableSharedFlow(replay=1) 或 StateFlow |
冷流避免重复请求 | stateIn 或 shareIn |
多页面共享数据源 | SharedFlow + replay / StateFlow 存在 singleton VM |
容易踩的坑
-
把事件放进 StateFlow → 新订阅者会立刻收到"旧事件"(粘性过头)。
用 SharedFlow(replay=0)。
-
冷流被多处收集重复打接口 → 用 stateIn/shareIn 升温并缓存。
-
下游慢导致挂起 → 给 SharedFlow 配 extraBufferCapacity 或对下游 collectLatest()。
-
忘记停止上游 → WhileSubscribed 能在无人订阅时自动取消上游。
一页小抄
scss
// 状态
val _state = MutableStateFlow(UiState())
val state: StateFlow<UiState> = _state
// 事件
val _event = MutableSharedFlow<UiEvent>(replay = 0, extraBufferCapacity = 64)
val event: SharedFlow<UiEvent> = _event
// 冷→热
val hot = cold.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), initial)
// 非阻塞发送事件
_event.tryEmit(UiEvent.Toast("Hi"))
// 收集(Fragment)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.state.collect { ... }
// 并行
launch { viewModel.event.collect { handle(it) } }
}
}