目录
一、工作中运用的例子:MutableStateFlow和MutableSharedFlow应该使用哪个?
二、冷热流是什么
一、工作中运用的例子
MutableStateFlow和MutableSharedFlow,value和emit
ini
private val _lightValue = MutableStateFlow(0f)
val lightValue = _lightValue.asStateFlow()
kotlin
override fun onSensorChanged(event: SensorEvent) {
if (event.sensor.type == Sensor.TYPE_LIGHT) {
val lux = event.values[0]
_lightValue.value = lux
}
}
ini
private val _lightValue = MutableSharedFlow<Float>()
val lightValue = _lightValue.asSharedFlow()
kotlin
override fun onSensorChanged(event: SensorEvent) {
if (event.sensor.type == Sensor.TYPE_LIGHT) {
val lux = event.values[0]
coroutineScope.launch {
_lightValue.emit(lux)
}
}
}
kotlin
override fun initObserve() {
// 设置光照变化回调
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED){
LightSensorManager.lightValue.collect{lux ->
mBinding.progressBar.setProgress((lux/2000)*100)//20000目标值。
mBinding.lightValue.text = " ${"%.1f".format(lux)} Lux"
mBinding.lightContent.text = updateBrightnessStatus(lux)
}
}
}
}
**他们的区别,什么时候应该使用哪个?他们的底层原理又是如何的呢?执行又是如何,每次collect之后,他们是如何执行呢?**只有了解了这些以后,我们写的代码性能才能更高,才稳定。
1.1 MutableStateFlow
比如我们上面的例子,是读取手机系统的光照强度,而这个光照强度的数值,是持续变化的一个状态量。

那么使用MutableStateFlow,会更加合适。因为他是新订阅者会立即收到当前最新值。
可以这样理解,比如这个状态量1,2,3,4,5这些值,那么当你collect以后,就会立马重新获取最新值。即使没有得到光亮值,也会有一个初始值。
他的引用场景非常广泛,比如 UI 状态、传感器当前值,只要是持续变化的都应该使用他。
value
与emit()
的使用差异
在这个例子当中,你使用那个都可以。 但协程内优先用emit(),非协程环境优先用value,协程内使用value可能抛出异常或导致 UI 更新问题。
- 多线程并发更新,协程内部,使用
emit()
,线程安全有保障,避免数据竞争 - 非协程环境(普通函数 / 回调),简单的同步更新,使用
value
无需启动协程,直接赋值更高效(少一层协程调度)
传感器的onSensorChanged
回调本身运行在后台线程,且是非协程环境,直接使用value
更合适
1.2 MutableSharedFlow
而MutableSharedFlow,虽然也可以在这个获取光照强度的例子中进行使用,但可能会存在一些问题,比如用户打开页面时,SharedFlow
默认不会发送任何数据,UI 会一直显示空白,直到光照发生变化才会首次更新。这显然不符合用户预期 ------ 打开页面就应该看到当前的光照值。因为MutableSharedFlow不能设置初始值。
虽然要解决这个问题,也可以手动配置replay = 1
(保留最近 1 条数据),但相比起来就会复杂很多。传感器可能在短时间内发送多次相同的 lux 值(如光照稳定时),SharedFlow
会将这些重复值全部发送给订阅者,导致 UI 频繁无效刷新(即使数值没变,也会执行setText
等操作)。
而StateFlow
会自动比较新旧值,只有当值真正变化时才通知订阅者,避免无效更新。
SharedFlow
需要手动配置缓冲区策略(如extraBufferCapacity
、onBufferOverflow
),否则在高频数据场景下可能出现:
- 缓冲区满时发送方挂起(导致传感器回调阻塞)
- 或数据丢失(取决于配置的溢出策略)
而StateFlow
无需这些配置,内部通过 "覆盖最新值" 的方式天然适配高频状态更新(光照传感器可能每秒触发多次)。
1.3原理和底层
MutableStateFlow
- 必须初始化一个默认值(如
0f
),始终持有最新值 - 新订阅者会立即收到当前最新值(无论何时订阅,都能获取 "现在" 的状态)
- 自动过滤重复值(连续相同的 lux 值不会触发无效更新)
- 状态数据(有当前值概念)→ 用
StateFlow
- 如传感器读数、UI 控件状态(开关状态、进度值)、用户信息等。这些数据的核心是 "当前值",且新订阅者需要立即知晓。
MutableSharedFlow
-
默认没有初始值,也不持有最新值
-
新订阅者只能收到订阅之后的新数据(默认配置下)
-
不会自动过滤重复值(即使连续发送相同 lux,也会全部传递)
-
事件流(一次性通知)→ 用
SharedFlow
-
如按钮点击、网络请求结果、日志输出等。这些数据的核心是 "发生过的事件",新订阅者无需关心订阅前的内容。
-
无状态特性:默认情况下,发送的事件不会被保存,错过的事件无法重放
-
背压处理 :当订阅者处理速度慢于事件产生速度时,根据配置的策略(如
BUFFER_OVERFLOW_SUSPEND
)决定是挂起、丢弃还是报错执行流程:
markdown
1. 协程中调用`_lightValue.emit(lux)` → 进入缓冲区
2. 检查缓冲区状态 → 若未满则存入,否则根据策略处理
3. 遍历所有活跃订阅者,将事件分发给每个`collect`协程
在光照传感器场景中,数据本质是 "状态" 而非 "事件",因此StateFlow
是更自然、更简洁的选择。SharedFlow
虽然能通过配置实现类似功能,但会引入不必要的复杂性,属于 "用错了工具"。
二、冷流和热流
2.1 冷流
典型例子 :flow { ... }
构建的普通流、网络请求流。
- 核心特性 :无订阅者时不产生数据,数据生产与订阅者绑定。
scss
// 冷流示例:只有collect时才会执行代码块
val coldFlow = flow {
println("开始产生数据") // 无订阅时不会执行
emit(1)
emit(2)
}
// 首次订阅:打印"开始产生数据"并收到1、2
coldFlow.collect { ... }
// 第二次订阅:再次打印"开始产生数据"并收到1、2(重新执行)
coldFlow.collect { ... }
2.2 热流
典型例子 :StateFlow
、SharedFlow
、传感器数据流、UI 事件流。
- 核心特性 :无论是否有订阅者,都会持续产生数据,数据生产与订阅者解耦。
StateFlow
和SharedFlow
本质上都是热流 ,这是它们与普通flow
的核心区别:
- 它们的数据产生不依赖订阅者(例如传感器无论是否被观察,都会持续产生数据)。
- 多个订阅者可以共享同一个数据流(类似 "直播被多人观看")。
类型 | 热流特性细节 |
---|---|
StateFlow |
始终持有最新状态(有 "当前值"),新订阅者会立即收到最新值 |
SharedFlow |
不持有状态(默认无 "当前值"),新订阅者默认只收到订阅后的新数据(可配置重放) |
传感器本身是 "热数据源"------ 无论是否有代码监听(订阅),硬件都会持续产生光照数据。 StateFlow
作为 "热流",完美适配这种特性:即使没有 UI 订阅,传感器数据仍会更新StateFlow
的value
;当 UI 恢复订阅时,能立即拿到最新的光照值(当前状态)。
如果误用冷流(如普通flow
),会出现问题:
scss
// 错误示例:用冷流接收传感器数据
val lightColdFlow = flow {
sensorManager.registerListener(...) // 冷流中注册传感器
// 发射数据...
}
// 问题:每次collect都会重新注册传感器,导致重复监听
lightColdFlow.collect { ... } // 第一次collect:注册传感器
lightColdFlow.collect { ... } // 第二次collect:再次注册,导致数据重复
- 冷流:适合 "按需产生数据" 的场景(如网络请求、数据库查询),数据生产与订阅强绑定。
- 热流:适合 "持续产生数据" 的场景(如传感器、UI 事件、状态变化),数据生产与订阅解耦。