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) } }
  }
}
相关推荐
weifont24 分钟前
自动化抓取和上架商品:能不动手,就少动手
运维·前端·自动化
米柆29 分钟前
HTML5 audio /video 完整事件监听模板
前端·html·html5
IT_陈寒1 小时前
Vite热更新失效?你可能也踩了这个坑
前端·人工智能·后端
程序员大阳2 小时前
副队长CSS教程(30)–CSS Grid 实战:实现复杂自适应表单布局
前端·css·自适应·grid·表单
Csvn2 小时前
微前端实现
前端
智码看视界2 小时前
C++智能指针全景剖析及面试20问
开发语言·c++·深度学习·面试·内存管理·智能指针
Chrischaan2 小时前
做 AI 塔罗小程序时,我为什么不让大模型自己抽牌:一次上线复盘
前端
骑着蜗牛撵大象3273 小时前
LangChain 工具调用从声明到闭环:给智能体接上真实的手脚
前端
数据掘金3 小时前
百度统计后台页面数据异常?从手动调用时机开始排查
前端
hunteritself3 小时前
10 层 4K 实时剪辑,国产工作站进入广电制作核心
大数据·前端·人工智能·深度学习·transformer