一、数据库使用冷流(普通Flow)和热流(stateIn转换)的区别
scss
val lightFlow = DataBase.getInstance()
.getLightDao()
.getData()
.flowOn(Dispatchers.IO) // 在 IO 线程执行查询
ini
val lightHistoryFlow = DataBase.getInstance().getLightDao().queryLimitCountF(50).map {
if (it.isEmpty()) {
LightData.fakeData(
listOf(
LightEntity(value =400f, time = Date()),
LightEntity(value =500f, time = Date()),
)
)
}
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = LightData.fakeData(emptyList())
)
1.1 两段代码的核心区别
特性 | lightFlow (普通 Flow) |
lightHistoryFlow (StateFlow) |
---|---|---|
流类型 | 冷流(Flow ) |
热流(通过stateIn 转换为StateFlow ) |
状态持有 | 不持有状态,无初始值 | 持有状态,有initialValue |
订阅行为 | 每次collect 都会触发数据源重新执行(如数据库查询) |
多次collect 共享同一数据流,数据源只执行一次 |
新订阅者 | 重新执行上游逻辑(重新查数据库) | 立即收到当前持有的状态值,不会重新执行上游 |
生命周期 | 与订阅者绑定,无订阅时上游逻辑停止 | 与指定的scope 绑定,在started 条件满足时保持活跃 |
lightFlow
(普通冷流) :
- 每次调用
collect
时,都会重新执行getData()
对应的数据库查询 - 没有 "当前值" 的概念,订阅者只能获取订阅之后产生的新数据
- 当所有订阅者取消订阅后,上游的数据库查询会停止(冷流特性)
执行流程:
订阅1 → 触发数据库查询 → 发射结果
订阅2 → 再次触发数据库查询 → 发射结果
所有订阅取消 → 数据库查询停止
lightHistoryFlow
(热流 StateFlow) :
- 通过
stateIn
将冷流转换为热流,数据库查询queryLimitCountF(50)
只会执行一次 - 结果会被缓存为 "状态",新订阅者立即收到当前缓存的状态(
initialValue
或最新结果) - 由
viewModelScope
和SharingStarted.WhileSubscribed(5000)
控制生命周期:当有活跃订阅时保持数据更新,无订阅 5 秒后停止上游,节省资源
执行流程:
首次订阅 → 触发数据库查询 → 结果缓存为状态 → 所有订阅者共享该状态
新订阅者加入 → 直接获取当前缓存状态(不查数据库)
无订阅5秒后 → 停止上游,但保留最后状态
再次订阅 → 立即返回保留的状态,同时重新启动上游更新
1.2具体建议
在你的健康数据场景中:
- 如果
lightFlow
是 "获取最新光照 :
建议改为StateFlow
,避免重复查询数据库:
ini
val LightFlow = DataBase.getInstance()
.getLightDao()
.getData()
.flowOn(Dispatchers.IO) // 在 IO 线程执行查询
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = null // 适合的初始值
)
lightHistoryFlow
的写法是合理的 :
历史记录数据适合作为 "状态" 缓存,且可能被多个 UI 组件订阅,stateIn
能有效减少数据库查询次数,提升性能。