Flow 热流

是什么

热流 指:无需收集者也能持续产出/保持数据 ,新的订阅者加入时可立即拿到最近值最近 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

容易踩的坑

  1. 把事件放进 StateFlow → 新订阅者会立刻收到"旧事件"(粘性过头)。

    用 SharedFlow(replay=0)。

  2. 冷流被多处收集重复打接口 → 用 stateIn/shareIn 升温并缓存。

  3. 下游慢导致挂起 → 给 SharedFlow 配 extraBufferCapacity 或对下游 collectLatest()。

  4. 忘记停止上游 → 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) } }
  }
}
相关推荐
PBitW16 小时前
element plus 使用细节 (二)
前端·vue·element plus·element使用细节
zcz160712782116 小时前
Web详解
前端
良木林16 小时前
JS函数进阶
开发语言·前端·javascript
HelloRevit17 小时前
让B站视频4倍速度播放
前端·javascript·音视频
SEO_juper17 小时前
E-E-A-T与现代SEO:赢得搜索引擎信任的完整策略
前端·搜索引擎·seo·数字营销·seo优化·谷歌seo
一点一木17 小时前
2025 前端 3D 选型指南:Three.js、Babylon.js、WebGPU 深度对比
前端·javascript·3d
岭子笑笑17 小时前
vant 4 暗黑主题源码阅读
前端
匆叔18 小时前
JavaScript 性能优化实战技术
前端·javascript
子兮曰18 小时前
🚀前端环境变量配置:10个让你少加班的实战技巧
前端·node.js·前端工程化
用户516816614584118 小时前
Uncaught ReferenceError: __VUE_PROD_HYDRATION_MISMATCH_DETAILS__ is not defined
前端·vue.js