位置:首页 > Kotlin > Kotlin Flow 讲清楚:Flow、StateFlow、SharedFlow 到底该怎么用

Kotlin Flow 讲清楚:Flow、StateFlow、SharedFlow 到底该怎么用

时间:2026-08-24  |  作者:风起客  |  阅读:0

目录

  1. 先理解 Flow 的数据管道模型
  2. 冷流与热流,到底差在哪里
  3. Flow、StateFlow、SharedFlow 分别适合什么场景
  4. 在 UI 层怎么安全收集 Flow
  5. 常用操作符该怎么理解
  6. 最后总结:三种 Flow 类型怎么选

前言

Kotlin Flow 在 Android 里几乎已经是异步数据处理的默认选项,但真正落到项目中,很多人最容易混淆的还是三件事:普通 Flow、StateFlow、SharedFlow 各自该管什么,以及它们在 UI 层该怎么收集才不踩生命周期问题。下面就按类型选择、使用场景和界面收集这三条线拆开讲清楚。

Kotlin Flow 已经成为 Android 里处理异步数据流的主流方案,它建立在 Kotlin 协程之上,常被用来替代部分 RxJavaLiveData 场景。很多初学者真正卡住的地方,不是会不会写 collect,而是分不清什么时候该用普通 Flow,什么时候该换成 StateFlowSharedFlow

这篇文章按“数据从哪里来、经过什么处理、最终怎么安全落到 UI”这条线来梳理。你可以据此判断三种 Flow 类型的职责边界,也能顺手把生命周期收集和常用操作符放到合适的位置。

先理解 Flow 的数据管道模型

把 Flow 看成一条异步数据管道会更直观,整个过程通常可以分成三段:

  • 上游(Upstream):负责生产数据,例如数据库读取、网络请求。
  • 中游(Intermediaries):负责处理数据,例如用 filter 过滤、用 map 转换。
  • 下游(Downstream):负责接收和消费数据,例如更新 UI。

这个划分的价值在于:当你排查性能、线程切换或生命周期问题时,能很快判断问题出在“数据源”“处理中间层”还是“界面消费层”。

冷流与热流,到底差在哪里

Flow 体系里最核心的一组概念,就是冷流和热流。

对比 Kotlin 冷流与热流,以及 Flow、StateFlow、SharedFlow 的典型职责
Kotlin Flow 类型选择图用对比图把冷流与热流的触发方式、共享方式和典型用途放在一张图里,方便快速判断该选哪一类。
特性Cold Flow(普通 Flow)Hot Flow(StateFlow / SharedFlow)
激活时机只有被收集(collect)时才开始运行无论有无收集者,它都在内存中活跃,行为上类似 LiveData
数据生产一对一(Unicast),每个收集者都会重新触发一次数据生产流程一对多(Multicast),多个收集者共享同一个数据源
典型场景耗时数据库读取、文件下载、简单异步转换UI 状态管理(StateFlow)、一次性事件(SharedFlow

如果你只记一个判断标准,可以记这一句:需要“每次收集都重新执行”的,用普通 Flow;需要“值或事件在内存里持续存在并可被多个观察者共享”的,用热流。

Flow、StateFlow、SharedFlow 分别适合什么场景

普通 Flow:适合按需执行的异步任务

普通 Flow 是最基础的类型,适合执行耗时任务,并按顺序发射多个值。它的特点是“没人收集就不运行”,因此非常适合那些明确由下游触发的工作流。

// 例子:倒计时
fun countdownFlow(): Flow = flow {
    for (i in 5 downTo 0) {
        emit(i) // 发射数据
        delay(1000) // 挂起函数,不阻塞线程
    }
}

像倒计时、一次数据库查询、一次文件下载这类逻辑,用普通 Flow 很顺手,因为每次进入收集流程都会完整执行一遍上游逻辑。

StateFlow:适合持续保存 UI 状态

StateFlow 可以看作是 LiveData 的现代替代品,特别适合 ViewModel 中的状态管理。

  • 特点:始终持有最新的一个状态值。
  • 收集行为:新的收集者加入时,会立刻收到当前最新值。
  • 使用场景:保存 UI 状态,例如 LoadingSuccessError
  • 和 LiveData 的区别StateFlow 必须有初始值,同时完整支持协程操作符。
// ViewModel 中
private val _uiState = MutableStateFlow("Initial State")
val uiState: StateFlow = _uiState.asStateFlow()

// 更新状态
_uiState.value = "New State"

如果一个值代表“当前界面此刻应该显示什么”,那它大概率应该是 StateFlow,而不是普通 Flow

SharedFlow:适合一次性事件分发

SharedFlow 更适合发送一次性事件,也就是那些“发生过就算处理完”的消息。

  • 特点:用于发送一次性事件(One-off events)。
  • 默认行为:默认不保留旧数据,除非额外配置 replay
  • 收集行为:新收集者不会收到过去已经发出的事件。
  • 典型场景:导航跳转、弹出 Toast、显示 Snackbar、服务器推送消息。
// ViewModel 中
private val _events = MutableSharedFlow()
val events: SharedFlow = _events.asSharedFlow()

// 发送事件 (suspend function)
_events.emit("Show Toast")

一个常见误区是把 Toast、页面跳转这类事件塞进 StateFlow。这样很容易在界面重建后重复消费旧值。遇到这类“一次触发、一次处理”的需求,通常应该优先考虑 SharedFlow

在 UI 层怎么安全收集 Flow

Flow 好用,但前提是收集方式要和生命周期对齐。否则页面退到后台后仍在收集,就很容易造成资源浪费,甚至引发界面更新时机错误。

展示 Activity、Fragment 与 Compose 中安全收集 Flow 的方式
Flow 的生命周期安全收集这一张聚焦 UI 层收集方式,突出 Views 与 Compose 的推荐写法。

Activity / Fragment:用 repeatOnLifecycle

在传统 Views 体系里,推荐使用 repeatOnLifecycle。它会在生命周期达到指定状态时开始收集,在状态低于该值时自动取消协程。

最常见的写法是使用 Lifecycle.State.STARTED:进入前台开始收集,进入 STOPPED 后停止接收数据。

// 在 Activity 或 Fragment 中
lifecycleScope.launch {
    // 只有当生命周期至少是 STARTED 时才执行块内代码
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        // 这两个流会并行收集
        launch {
            viewModel.uiState.collect { state ->
                // 更新 UI
            }
        }
        launch {
            viewModel.events.collect { event ->
                // 处理事件
            }
        }
    }
}

这里把 uiStateevents 分别放进两个 launch 中并行收集,是非常常见也非常实用的模式。

Compose:用 collectAsStateWithLifecycle()

在 Jetpack Compose 中,通常直接使用 collectAsStateWithLifecycle()。它会把 Flow 转成 Compose 可用的 State,同时感知生命周期,减少手动处理的负担。

@Composable
fun MyScreen(viewModel: MyViewModel) {
    // 需要引入依赖: androidx.lifecycle:lifecycle-runtime-compose
    val state by viewModel.uiState.collectAsStateWithLifecycle()

    Text(text = state)
}

如果页面展示的是持续变化的状态,Compose + collectAsStateWithLifecycle() 基本就是当前最顺手的组合。

常用操作符该怎么理解

Flow 的强大之处,不只在于发数据,还在于可以沿着数据流一路加工。下面这些操作符最常见:

转换类:改造数据结构

  • map:做数据映射。
  • transform:做更灵活的自定义转换。

过滤类:减少无效更新

  • filter:按条件过滤。
  • debounce:防抖,搜索框场景很常见。
  • distinctUntilChanged:过滤重复值;StateFlow 默认自带这一特性。

组合类:处理多个数据源

  • combine:合并多个流。
  • zip:按配对方式组合多个流。
  • flatMapLatest:切换流,适合“搜索词变化时取消旧请求、发起新请求”这类场景。

异常处理与线程切换

  • catch:捕获上游异常。
  • flowOn(Dispatchers.IO):指定上游代码在 IO 线程执行。

一个实用理解方式是:catchflowOn 往往都更偏“上游控制”;UI 层应该尽量专注于收集结果,而不是承担过多数据加工责任。

最后总结:三种 Flow 类型怎么选

  • 需要按需触发、每次收集都重新执行任务:选普通 Flow
  • 需要持续保存并分发最新 UI 状态:选 StateFlow
  • 需要发送导航、Toast、Snackbar 这类一次性事件:选 SharedFlow

如果再配合 repeatOnLifecyclecollectAsStateWithLifecycle() 做生命周期感知收集,Flow 在 Android 项目里的可维护性会明显高于很多早期的异步方案。

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多