Flow热流冷流知识梳理

一、核心基础:冷流 vs 热流(最关键的底层逻辑)

1. 本质定义(判断标准)
维度 冷流(Cold Flow) 热流(Hot Flow)
核心特征 订阅驱动:有订阅者才启动上游,无订阅者上游停止 独立运行:上游不依赖订阅者,有无订阅者都可能运行
订阅者 - 上游关系 一对一:每个订阅者触发独立的上游实例 一对多:所有订阅者共享同一个上游实例
数据接收规则 订阅后才能收到后续发射的数据(错过的收不到) 无论何时订阅,都能收到订阅后的新数据(可配置缓存)
典型代表 普通 Flow(flow {...})、flowOf、asFlow 等 StateFlow、SharedFlow、Channel.asFlow ()(特殊)
2. 代码验证(一眼看懂区别)
kotlin 复制代码
// 冷流示例:每个订阅者触发一次上游(打印一次"冷流上游启动")
val coldFlow = flow {
    println("冷流上游启动") // 订阅时才执行
    emit(1)
    emit(2)
}

// 订阅1次 → 打印"冷流上游启动"
coldFlow.collect { println("订阅者1:$it") }
// 订阅2次 → 再次打印"冷流上游启动"(上游重复执行)
coldFlow.collect { println("订阅者2:$it") }

// 热流示例:所有订阅者共享上游(只打印一次"热流上游启动")
val hotFlow = coldFlow.shareIn(
    scope = CoroutineScope(Dispatchers.Default),
    started = SharingStarted.Eagerly,
    replay = 1
)

// 订阅1次 → 打印"热流上游启动"
hotFlow.collect { println("热流订阅者1:$it") }
// 订阅2次 → 不打印"热流上游启动"(共享已有上游)
hotFlow.collect { println("热流订阅者2:$it") }
3. 关键结论
  • 冷流的核心问题:多订阅者会重复执行上游逻辑(比如多次发起网络请求、多次监听 WebSocket 状态),这是 "性能浪费" 的根源;
  • 热流的核心价值:共享上游,无论多少订阅者,上游只执行一次。

二、热流的具体实现:SharedFlow vs StateFlow

StateFlow 是特殊的 SharedFlow,两者都是热流,核心区别如下:

特性 SharedFlow StateFlow
初始值 可选(无默认值) 必须有初始值(创建时指定)
缓存(replay) 可自定义(比如 replay=3 缓存 3 个值) 固定 replay=1(只缓存最新值)
发射规则 可以发射相同值(多次 emit (1) 都会触发) 只发射与当前值不同的新值(emit (1) 后再 emit (1) 不触发)
典型场景 事件通知(比如点击事件、网络请求结果) 状态存储(比如 UI 状态、WebSocket 连接状态、网络请求 ID)

代码示例

kotlin 复制代码
// 1. StateFlow(存储网络请求ID状态,有初始值)
val _currentRequestId = MutableStateFlow<String?>(null) // 必须有初始值
val currentRequestId: StateFlow<String?> = _currentRequestId

// 只有值变化时才触发订阅者(重复emit相同值不触发)
_currentRequestId.value = "req_123" // 触发订阅者
_currentRequestId.value = "req_123" // 不触发订阅者

// 2. SharedFlow(发送WebSocket连接事件,可重复发射)
val _webSocketEvents = MutableSharedFlow<WebSocketEvent>(
    replay = 0, // 不缓存事件,错过就没了
    extraBufferCapacity = 0
)
val webSocketEvents: SharedFlow<WebSocketEvent> = _webSocketEvents

// 重复发射相同事件也会触发订阅者
_webSocketEvents.emit(WebSocketEvent.Connected)
_webSocketEvents.emit(WebSocketEvent.Connected)

三、关键操作符:是否改变 Flow 的冷热属性?

核心结论combine/map/filter/merge/zip/debounce所有基础操作符,都不会改变原 Flow 的冷热属性,也不会自动共享上游。

1. 重点验证:combine 对冷热属性的影响
kotlin 复制代码
// 上游1:StateFlow(热流)
val apiInfoFlow: StateFlow<ApiResponse?> = apiService.infoFlow
// 上游2:普通Flow(冷流)
val socketFlow: Flow<WebSocketState> = webSocketManager.socketFlow

// combine后的流:冷流!(只要有一个上游是冷流,combine后就是冷流)
val combinedFlow = combine(apiInfoFlow, socketFlow) { requestData, state ->
    // 这个lambda,每个订阅者都会触发独立执行
    println("combine逻辑执行") 
    Pair(requestData, state)
}

// 订阅1次 → 打印"combine逻辑执行"
combinedFlow.collect { ... }
// 订阅2次 → 再次打印"combine逻辑执行"(上游重复执行)
combinedFlow.collect { ... }
2. 各操作符的统一规则
操作符 是否改变冷热属性 是否重复执行上游 核心影响
map/filter 是(冷流场景) 只是转换 / 过滤数据,不共享
combine/merge/zip 是(冷流场景) 包装多个流,冷流特性保留
debounce/distinctUntilChanged 是(冷流场景) 只是限流 / 去重,不共享
shareIn/stateIn 是(冷→热) 否(热流场景) 转为热流,共享上游

四、性能优化:避免上游 "多开"

核心诉求是 "不浪费性能",本质就是避免冷流的多订阅者重复执行上游 ,唯一解决方案:shareIn/stateIn 将冷流转热流。

1. 最优写法(通用场景)
kotlin 复制代码
// 步骤1:定义冷流(combine后的流)
val coldCombinedFlow = combine(
    apiService.infoFlow, // 可能是StateFlow(热)
    webSocketManager.socketFlow // 可能是冷流
) { requestInfo, _ ->
    // 业务逻辑处理
    Pair(requestInfo, _)
}.filterNotNull()

// 步骤2:转为热流(核心:避免多订阅者重复执行combine逻辑)
val hotCombinedFlow = coldCombinedFlow.shareIn(
    scope = appCoroutineScope, // 建议用生命周期绑定的Scope(如viewModelScope)
    started = SharingStarted.WhileSubscribed(5000), // 5秒超时释放
    replay = 1 // 缓存最新值,新订阅者立即拿到
)

// 多次订阅 → 上游combine逻辑只执行一次
hotCombinedFlow.collect { ... } // 第一次订阅启动上游
hotCombinedFlow.collect { ... } // 第二次订阅共享上游,不重复执行
2. WhileSubscribed(5000) 的深层价值
  • 避免 "页面旋转 / 短时间切后台" 导致上游重启(5 秒内重新订阅,上游不停止);
  • 避免 "长时间无订阅" 导致资源浪费(5 秒后停止上游,释放网络请求 / 网络监听)。
3. 实战避坑点
  1. 误区:"上游是 StateFlow(热流),combine 后就是热流" → 错!combine 后的流依然是冷流,多订阅者会重复执行 combine 内的逻辑;
  2. 误区:"加了 filterNotNull 就是热流" → 错!filter 只是过滤,不改变冷热属性;
  3. 关键 :只要流需要被多个地方订阅(比如 Fragment+ViewModel),就必须用 shareIn/stateIn 转为热流。

总结

冷热流核心本质 :冷流是 "数据产生逻辑的封装",本身不主动产生数据,仅在调用collect订阅时执行内部逻辑、创建独立的 "数据生产者",多订阅者会触发多个独立生产者(如多次发起独立网络请求),适合需要 "多次独立执行" 的场景;热流的核心是 "全局唯一的数据流",上游不依赖订阅者独立运行,所有订阅者共享同一个生产者,能从根源避免重复执行上游逻辑造成的性能浪费。

操作符核心规则 :map、combine、filter 等基础操作符不会改变 Flow 的冷热属性,冷流经其处理后仍为冷流;只有shareIn/stateIn能将冷流转为热流,是实现上游共享的唯一方式。

性能优化核心方案 :多订阅场景(如 Fragment+ViewModel 共用数据流)下,需通过shareIn/stateIn将冷流(尤其是 combine 后的冷流)转为热流;配合WhileSubscribed(5000)使用可平衡响应速度与资源释放,既避免短时间页面切换导致的上游重启,又能在长时间无订阅时释放网络、WebSocket 监听等资源。

热流选型原则 :基于数据类型选择适配的热流实现 ------ 状态型数据(如网络请求 ID、WebSocket 连接状态)优先用StateFlow(有初始值、固定 replay=1、自动去重重复值,适配 "存储单一最新状态" 需求);事件型数据(如网络请求结果、WebSocket 连接事件)优先用SharedFlow(无强制初始值、可自定义缓存、支持重复发射相同事件);已有冷流需转为全局唯一数据流时,用shareIn配合生命周期 Scope、WhileSubscribed(5000)和合理的 replay 值改造。

相关推荐
A-花开堪折2 小时前
RK3568 Android 11 驱动开发(四):添加产品配置和内核设备树选择
android·驱动开发
TheNextByte12 小时前
如何将照片从Android传输到闪存驱动器
android
JMchen1233 小时前
Android Activity管理工具类
android·java·学习·移动开发·android-studio
shix .3 小时前
spiderdemo-T8字体反扒
android
青小莫3 小时前
C++之模板
android·java·c++
装不满的克莱因瓶3 小时前
Android Studio 的模拟器如何上传本地图片到手机相册
android·智能手机·android studio
三金121384 小时前
深入解析MySQL EXPLAIN
android
_李小白4 小时前
【Android 美颜相机】第十一天:GPUImageFilter解析
android·数码相机
dawudayudaxue5 小时前
sqlite在安卓下使用ndk的交叉编译
android·数据库·sqlite