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) } }
  }
}
相关推荐
我是华为OD~HR~栗栗呀3 分钟前
测试转C++开发面经(华为OD)
java·c++·后端·python·华为od·华为·面试
小菜全8 分钟前
《WebAssembly:前端开发的新可能》
前端·javascript
Dream it possible!13 分钟前
LeetCode 面试经典 150_哈希表_快乐数(45_202_C++_简单)(哈希表;快慢指针)
leetcode·面试·散列表
余防13 分钟前
CSRF跨站请求伪造
前端·安全·web安全·csrf
兮山与17 分钟前
前端2.0
前端
南风木兮丶24 分钟前
Vue 项目安装 @antfu/eslint-config 保姆级教程
前端·javascript·vue.js
万少37 分钟前
记 HarmonyOS 开发中的一个小事件 怒提华为工单
前端·harmonyos
未来之窗软件服务39 分钟前
万象EXCEL开发(六)excel单元格运算逻辑 ——东方仙盟金丹期
前端·excel·仙盟创梦ide·东方仙盟·万象excel
Mintopia1 小时前
🚀 Cesium-Kit:10 秒为你的 Cesium 项目添加动态光效标记
前端·javascript·cesium
Mintopia1 小时前
🌩️ 云边协同架构下的 WebAI 动态资源调度技术
前端·javascript·aigc