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) } }
  }
}
相关推荐
We་ct15 小时前
LeetCode 36. 有效的数独:Set实现哈希表最优解
前端·算法·leetcode·typescript·散列表
weixin_3954489115 小时前
main.c_cursor_0129
前端·网络·算法
2401_8590490815 小时前
git submodule update --init --recursive无法拉取解决
前端·chrome·git
这是个栗子16 小时前
【Vue代码分析】前端动态路由传参与可选参数标记:实现“添加/查看”模式的灵活路由配置
前端·javascript·vue.js
刘一说16 小时前
Vue 动态路由参数丢失问题详解:为什么 `:id` 拿不到值?
前端·javascript·vue.js
win x16 小时前
JavaSE(基础)高频面试点及 知识点
java·面试·职场和发展
熊猫钓鱼>_>16 小时前
动态网站发布部署核心问题详解
前端·nginx·容器化·网页开发·云服务器·静态部署
方也_arkling16 小时前
elementPlus按需导入配置
前端·javascript·vue.js
编程彩机17 小时前
互联网大厂Java面试:从分布式缓存到消息队列的技术场景解析
java·redis·面试·kafka·消息队列·微服务架构·分布式缓存
我的xiaodoujiao17 小时前
使用 Python 语言 从 0 到 1 搭建完整 Web UI自动化测试学习系列 44--将自动化测试结果自动推送至钉钉工作群聊
前端·python·测试工具·ui·pytest