当前位置: 首页 > backend >正文

flow-两种SharingStarted策略的区别示例

一 代码示例

viewModel.kt:// 上游数据源 - 模拟温度传感器
private val temperatureSource = flow {var temp = 20while(true) {emit(temp++)delay(1000)println("上游发射温度: $temp") // 日志观察发射}
}// WhileSubscribed - 有订阅者才收集
val temperature1 = temperatureSource.stateIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(5000),initialValue = 0
)// Eagerly - 立即开始收集
val temperature2 = temperatureSource.stateIn(scope = viewModelScope,started = SharingStarted.Eagerly,initialValue = 0
)Fragment.kt:lifecycleScope.launch {
delay(5000)
viewModel.temperature1.collect { temp ->binding.tempText.text = "$temp°C"
}
}lifecycleScope.launch {
delay(5000)
viewModel.temperature2.collect { temp ->binding.tempText.text = "$temp°C"
}
}

二 日志

1 SharingStarted.Eagerly 订阅和不订阅 ,上游流都会执行发送
在这里插入图片描述
在这里插入图片描述

2 SharingStarted.WhileSubscribed(5000) ,不订阅
在这里插入图片描述

3 SharingStarted.WhileSubscribed(5000),进行订阅 ,上游流才会执行发送
在这里插入图片描述
3.2 当前界面不可见,上游还会执行5次(5s)才停止
在这里插入图片描述

二 解释

为什么需要testViewModel.temperature2执行一下,才执行 Logger.getLogger().d(“上游发射温度: $temp”)这个代码

  1. StateFlow 的初始化机制
class TestViewModel: ViewModel() {// 这里只是定义了属性,但实际上并没有立即执行val temperature2 = temperatureSource.stateIn(scope = viewModelScope,started = SharingStarted.Eagerly,initialValue = 0)
}
  1. Kotlin 属性的延迟初始化
  • Kotlin 的属性默认是懒加载的
  • 只有在首次访问属性时才会执行初始化代码
  • stateIn 操作符的实际执行要等到属性被首次访问
  1. 正确的立即收集方式
class TestViewModel: ViewModel() {val temperature2 = temperatureSource.stateIn(...)init {// 在 init 块中触发初始化temperature2  // 这行代码会触发实际的收集}
}
  1. 当前代码的问题
// Fragment中
launch {testViewModel.temperature2  // 如果没有这行,属性就不会被初始化
}

要解决这个问题,有两种方案:

// 方案1:在 ViewModel 的 init 块中触发
class TestViewModel: ViewModel() {val temperature2 = temperatureSource.stateIn(...)init {temperature2  // 确保立即初始化}
}// 方案2:使用 property delegate
class TestViewModel: ViewModel() {val temperature2 by lazy {temperatureSource.stateIn(...)}
}

所以,SharingStarted.Eagerly 的"立即"是指:一旦 StateFlow 被初始化,就会立即开始收集,而不是等待下游订阅者。但初始化本身仍需要一次属性访问来触发。

四 收集的含义

这里的"收集"指的就是 temperature1temperature2temperatureSource 获取值的过程。详细解释一下数据流向:

// 1. 上游数据源(冷流) - 产生数据
private val temperatureSource = flow {var temp = 20while(true) {emit(temp++)  // 发射数据delay(1000)Logger.getLogger().d("上游发射温度: $temp")}
}// 2. 转换为 StateFlow(热流) - 收集并分发数据
val temperature2 = temperatureSource.stateIn(scope = viewModelScope,started = SharingStarted.Eagerly,  // 立即开始收集上游数据initialValue = 0
)// 3. Fragment中的使用 - 消费数据
launch {testViewModel.temperature2.collect { temp ->Logger.getLogger().d("收到温度: $temp")}
}

数据流向图示:

temperatureSource (上游)↓ emit数据
temperature2 (StateFlow)↓ collect数据
Fragment (下游)

关键点:

  1. temperatureSource 负责产生数据
  2. temperature2 通过 stateIn 操作符收集这些数据
  3. Eagerly 模式下,一旦 StateFlow 被初始化,就开始收集上游数据
  4. Fragment 可以通过 collect 获取这些已经被收集的数据

所以这里的"收集"包含两层含义:

  • StateFlow 收集上游 Flow 的数据
  • Fragment 收集 StateFlow 的数据

解释一下这里的收集和订阅的关系:

  1. 数据流的三个层次
// 1. 源头(上游) - 产生数据
private val temperatureSource = flow {emit(temp++)  // 发射数据
}// 2. 中间层 - StateFlow
val temperature2 = temperatureSource.stateIn(...)  // 收集上游并暂存// 3. 终点(下游) - Fragment
launch {temperature2.collect { temp ->  // 订阅并收集StateFlow的数据Logger.getLogger().d("收到温度: $temp")}
}
  1. collect 的两种角色
  • StateFlow 对上游的 collect:收集并存储数据
  • Fragment 对 StateFlow 的 collect:订阅并消费数据
  1. 为什么下游也用 collect
  • Kotlin Flow API 中统一使用 collect 操作符来获取数据
  • 虽然概念上是"订阅",但技术实现上仍是"收集"
  • collect 本质是一个挂起函数,会持续接收数据流
  1. 完整流程示意
temperatureSource ----emit---→ temperature2 ----emit---Fragment(收集存储)              (订阅收集)

所以,虽然从概念上讲 Fragment 是"订阅者",但在 Flow API 中统一使用 collect 操作符来实现数据获取,无论是中间层的 StateFlow 还是最终的消费者。

http://www.xdnf.cn/news/7398.html

相关文章:

  • kotlin Flow的技术范畴
  • 解决软件连接RabbitMQ突发System.IO.IOException: 无法从传输连接中读取数据: 远程主机强迫关闭了一个现有的连接异常
  • RabbitMQ-高级
  • nginx 漏洞修复 CVE-2024-7347 CVE-2025-23419
  • 深入理解蒙特卡洛树搜索(MCTS):python从零实现
  • SQL:多列匹配(Multiple-column Matching)
  • Mybatis操作数据库(2)
  • 看之前熟悉双亲委派加载机制,看之后了解双亲委派加载机制
  • HarmonyOS实战:自定义时间选择器
  • 仿微钙化结石体模的详细讲解
  • 学习源码?
  • 详解受约束的强化学习(一、入门学习)
  • 【深度学习新浪潮】什么是多模态大模型?
  • 什么是Monorepo(单体仓库)(monolithic repository)
  • 隨筆 20250519 基于MAUI Blazor整合SQLite数据库与Star打印机的详细步骤
  • 【机器学习】线性回归和损失函数
  • WebSphere Application Server(WAS)8.5.5教程第五讲
  • Python网络爬虫入门指南
  • Git初始化本地已有项目,并推送到远端Git仓库完整操作指南
  • ebpf简介
  • Visual Studio解决方案构建三剑客:生成/重新生成/清理完全指南(实战经验总结)
  • 60天python训练计划----day30
  • GloVe 模型讲解与实战
  • 淘宝商品详情PAI接口可以获取哪些信息?
  • 人工智能重塑医疗健康:从辅助诊断到个性化治疗的全方位变革
  • React 个人笔记 Hooks编程
  • android双屏之副屏待机显示图片
  • leetcode 每日一题 1931. 用三种不同颜色为网格涂色
  • autoDL算力云装Xinference[坑与步骤]
  • JDK 21新特性详解