kotlin冷流热流的区别

在 Kotlin 中,LiveData 和 Flow 是 Android 开发中核心的异步数据处理工具,二者的数据发送/发射方法因设计理念(LiveData 面向生命周期、Flow 面向协程异步流)不同而差异显著。下面分模块梳理它们的发送/发射方法,并对比核心区别。

一、LiveData 的数据发送方法

LiveData 是 Jetpack 提供的生命周期感知型可观察数据持有者 ,核心是向观察者(Observer)发送数据,主要发送方法有 3 类,且均为线程安全(内部通过 MainThread 调度)。

1. setValue(T) - 主线程发送(必须)
  • 用法 :直接调用 liveData.setValue(data),仅能在主线程(UI 线程) 执行。
  • 行为:立即将数据分发给活跃状态(Resumed/Started)的观察者,若观察者非活跃则暂存数据,待其活跃后补发。
  • 异常 :若在子线程调用,会抛出 IllegalStateException
2. postValue(T) - 线程无关发送(异步)
  • 用法 :调用 liveData.postValue(data),可在任意线程执行。
  • 行为
    • 内部通过 Handler 将数据发送任务切换到主线程执行;
    • 若短时间内多次调用,仅最后一次的值会生效(中间值被合并);
    • 数据分发时机为下一次主线程消息循环,属于异步操作。
  • 场景:子线程更新 LiveData(如网络请求、数据库查询后)。
3. emit()(LiveData 协程扩展)- 协程内发送
  • 背景 :Jetpack 提供 lifecycle-livedata-ktx 扩展,支持协程中通过 emit 发送数据。

  • 用法 :通过 liveData { ... } 构建器结合 emit

    kotlin 复制代码
    val userLiveData = liveData {
        val user = repo.getUser() // 协程内执行异步操作
        emit(user) // 发送数据(自动切换到主线程)
        emit(anotherUser) // 可多次发送
    }
  • 行为

    • 仅能在 liveData 构建器的协程作用域内调用;
    • 自动切换到主线程分发数据,无需手动处理线程;
    • 支持挂起函数,可等待异步操作完成后发送。
LiveData 发送方法核心对比
方法 线程限制 执行时机 多次调用处理 适用场景
setValue 仅主线程 同步 每次都分发 主线程即时更新 UI
postValue 任意线程 异步 合并为最后一次 子线程更新数据(非高频)
emit 协程内(任意) 挂起同步 每次都分发 协程异步操作后发送数据

二、Flow 的数据发射方法

Flow 是 Kotlin 协程提供的冷流(Cold Stream) ,核心是通过 emit 系列方法发射数据,完全基于协程,需在挂起函数/协程作用域内执行,且本身不绑定线程(需通过 flowOn 指定发射线程)。

1. emit(T) - 基础发射(单次值)
  • 用法 :在 flow { ... } 构建器内调用 emit(data),是 Flow 最核心的发射方法。

    kotlin 复制代码
    val numberFlow = flow {
        for (i in 1..3) {
            delay(100) // 挂起函数,不阻塞线程
            emit(i) // 发射单个值
        }
    }
  • 行为

    • 挂起函数,发射后挂起直到观察者接收;
    • 冷流特性:仅当观察者(collect)订阅时才开始发射;
    • 支持背压(Backpressure):根据观察者处理能力调整发射速率。
2. emitAll(Flow<T>) - 发射另一个流的所有值
  • 用法 :在 flow 构建器内,通过 emitAll 发射另一个 Flow 的全部数据:

    kotlin 复制代码
    val parentFlow = flow {
        emit("开始")
        emitAll(childFlow) // 发射子流的所有值
        emit("结束")
    }
    val childFlow = flow { emit(1); emit(2) }
  • 行为

    • 挂起直到传入的 Flow 发射完成;
    • 按顺序发射:先发射当前流的已有值,再发射子流的所有值,最后继续当前流。
3. tryEmit(T) - 非挂起发射(无背压)
  • 用法 :在 MutableSharedFlow/MutableStateFlow(热流)中使用,非挂起函数:

    kotlin 复制代码
    val sharedFlow = MutableSharedFlow<Int>()
    // 任意协程/挂起函数外(但仍需协程作用域)
    sharedFlow.tryEmit(1)
  • 行为

    • 非挂起,立即尝试发射数据;
    • 无背压:若观察者处理能力不足,可能直接失败(返回 false);
    • 仅适用于热流(SharedFlow/StateFlow) ,冷流 flow { } 不支持。
4. StateFlow.value - 状态流赋值(特殊发射)
  • StateFlow 是特殊的 SharedFlow,代表"单一状态",通过 value 属性赋值即发射数据:

    kotlin 复制代码
    val stateFlow = MutableStateFlow(0)
    stateFlow.value = 1 // 发射新值(等同于发射)
  • 行为

    • 仅当新值与旧值不同时才发射;
    • 有初始值,始终持有最新值;
    • 线程安全,可在任意线程赋值(但建议主线程更新 UI 相关状态)。
Flow 发射方法核心对比
方法 类型 是否挂起 背压支持 适用场景
emit 冷流/热流 冷流基础发射,按需分发
emitAll 冷流/热流 合并多个流的发射逻辑
tryEmit 仅热流 非挂起场景,无需背压
StateFlow.value 仅状态流 单一状态更新,UI 状态同步

三、LiveData vs Flow 发送/发射方法的核心区别

维度 LiveData Flow
线程模型 强绑定主线程(setValue/postValue 自动切主线程) 无线程绑定,通过 flowOn 指定发射线程,collect 指定接收线程
生命周期感知 天生支持(仅向活跃观察者发送) 需结合 lifecycle.repeatOnLifecycle 实现生命周期感知
冷/热特性 半热(有数据时,活跃观察者立即接收) 冷流(flow { })/热流(SharedFlow/StateFlow)
背压支持 无(多次 postValue 会合并) 冷流原生支持背压,热流可配置背压策略
挂起/非挂起 setValue/postValue 非挂起,emit 挂起 核心 emit 挂起,tryEmit/value 非挂起
多次发送处理 postValue 合并,setValue 即时 冷流按序发射,StateFlow 仅发射不同值
异常处理 无原生支持(异常会崩溃) 支持 catch 操作符捕获异常

四、选型建议

  1. 简单 UI 状态更新 :优先用 StateFlow(替代 LiveData),结合 repeatOnLifecycle 感知生命周期;
  2. 一次性异步请求(如网络) :用冷流 flow { } + emit,订阅后自动取消;
  3. 跨页面/多观察者事件分发 :用 SharedFlow + tryEmit
  4. 兼容旧代码 :LiveData + emit 构建器,逐步迁移到 Flow。

总结:LiveData 的发送方法围绕"主线程、生命周期"设计,功能简单;Flow 的发射方法围绕"协程、流、背压"设计,更灵活且覆盖更多异步场景,是目前 Android 异步数据处理的主流选择。

相关推荐
moxiaoran57532 小时前
Go语言的map
开发语言·后端·golang
小信啊啊2 小时前
Go语言数组
开发语言·后端·golang
qq_336313932 小时前
Java基础-Stream流
java·开发语言·windows
superman超哥2 小时前
仓颉语言中异常捕获机制的深度剖析与工程实践
c语言·开发语言·后端·python·仓颉
Digitally2 小时前
如何将安卓应用导出到电脑/PC
android
再__努力1点2 小时前
【76】Haar特征的Adaboost级联人脸检测全解析及python实现
开发语言·图像处理·人工智能·python·算法·计算机视觉·人脸检测
one9962 小时前
C# 的进程间通信(IPC,Inter-Process Communication)
开发语言·c#
跟着珅聪学java2 小时前
以下是使用JavaScript动态拼接数组内容到HTML的多种方法及示例:
开发语言·前端·javascript
小鸡吃米…2 小时前
Python - 扩展
开发语言·python