Kotlin 04Flow stateIn 和 shareIn的区别

一 Kotlin Flow 中的 stateIn 和 shareIn

一、简单比喻理解

想象一个水龙头(数据源)和几个水杯(数据接收者):

  • 普通 Flow(冷流):每个水杯来接水时,都要重新打开水龙头从头放水
  • stateIn/shareIn(热流):水龙头一直开着,水存在一个水池里,任何水杯随时来接都能拿到水

二、stateIn 是什么?

就像手机的状态栏

  • 总是显示最新的一条信息(有当前值
  • 新用户打开手机时,立刻能看到最后一条消息
  • 适合用来表示"当前状态",比如:
    • 用户登录状态(已登录/未登录)
    • 页面加载状态(加载中/成功/失败)
    • 实时更新的数据(如股票价格)

代码示例:

// 创建一个永远知道当前温度的温度计
val currentTemperature = sensorFlow.stateIn(scope = viewModelScope,  // 在ViewModel生命周期内有效started = SharingStarted.WhileSubscribed(5000), // 5秒无订阅就暂停initialValue = 0 // 初始温度0度)// 在Activity中读取(总是能拿到当前温度)
textView.text = "${currentTemperature.value}°C"

三、shareIn 是什么?

就像广播电台

  • 不保存"当前值"(没有.value属性)
  • 新听众打开收音机时,可以选择:
    • 从最新的一条新闻开始听(replay=1)
    • 只听新新闻(replay=0)
  • 适合用来处理"事件",比如:
    • 显示Toast提示
    • 页面跳转指令
    • 一次性通知

代码示例:

// 创建一个消息广播站
val messages = notificationFlow.shareIn(scope = viewModelScope,started = SharingStarted.Lazily, // 有人收听时才启动replay = 1 // 新听众能听到最后1条消息)// 在Activity中收听广播
lifecycleScope.launch {messages.collect { msg ->Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()}
}

四、主要区别对比

特性stateIn (状态栏)shareIn (广播电台)
有无当前值有(.value 直接访问)无(必须通过collect接收)
新订阅者立即获得最新值可配置获得最近N条(replay)
典型用途持续更新的状态(如用户积分)一次性事件(如"购买成功"提示)
内存占用始终保存最新值按需缓存(可配置)
是否热流

五、为什么要用它们?

  1. 节省资源:避免重复计算(多个界面可以共享同一个数据源)

    • ❌ 不用时:每个界面都单独请求一次网络数据
    • ✅ 使用后:所有界面共享同一份网络数据
  2. 保持一致性:所有订阅者看到的数据完全相同

    • 比如用户头像更新后,所有界面立即同步
  3. 自动管理生命周期

    • 当Activity销毁时自动停止收集
    • 当配置变更(如屏幕旋转)时保持数据不丢失

六、生活场景类比

场景1:微信群(stateIn)

  • 群里最后一条消息就是当前状态(.value)
  • 新成员进群立刻能看到最后一条消息
  • 适合:工作群的状态同步

场景2:电台广播(shareIn)

  • 主播不断发送新消息
  • 听众打开收音机时:
    • 可以设置是否听之前的回放(replay)
    • 但无法直接问"刚才最后一首歌是什么"(无.value)
  • 适合:交通路况实时播报

七、什么时候用哪个?

用 stateIn 当:

  • 需要随时知道"当前值"
  • 数据会持续变化且需要被多个地方使用
  • 例如:
    • 用户登录状态
    • 购物车商品数量
    • 实时位置更新

用 shareIn 当:

  • 只关心新事件,不关心历史值
  • 事件可能被多个接收者处理
  • 例如:
    • "订单支付成功"通知
    • 错误提示消息
    • 页面跳转指令

八、超简单选择流程图

要管理持续变化的状态吗?是 → 需要直接访问当前值吗?是 → 用 stateIn否 → 用 shareIn(replay=1)否 → 这是一次性事件吗?是 → 用 shareIn(replay=0)

记住这个简单的口诀:
“状态用state,事件用share,想要回放加replay”

二 Kotlin Flow 的 shareInstateIn 操作符完全指南

在 Kotlin Flow 的使用中,shareInstateIn 是两个关键的操作符,用于优化流的共享和状态管理。本教程将深入解析这两个操作符的使用场景、区别和最佳实践。

一、核心概念解析

1. 冷流 vs 热流

  • 冷流 (Cold Flow):每个收集者都会触发独立的执行(如普通的 flow{} 构建器)
  • 热流 (Hot Flow):数据发射独立于收集者存在(如 StateFlowSharedFlow

2. 为什么需要 shareIn/stateIn?

  • 避免对上游冷流进行重复计算
  • 多个收集者共享同一个数据源
  • 将冷流转换为热流以提高效率

二、stateIn 操作符详解

基本用法

val sharedFlow: StateFlow<Int> = flow {// 模拟耗时操作emit(repository.fetchData())
}.stateIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(),initialValue = 0
)

参数说明:

  • scope:共享流的协程作用域(通常用 viewModelScope
  • started:共享启动策略(后文详细讲解)
  • initialValue:必须提供的初始值

特点:

  1. 总是有当前值(通过 value 属性访问)
  2. 新收集者立即获得最新值
  3. 适合表示 UI 状态

使用场景示例:

用户个人信息状态管理

class UserViewModel : ViewModel() {private val _userState = repository.userUpdates() // Flow<User>.map { it.toUiState() }.stateIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(5000),initialValue = UserState.Loading)val userState: StateFlow<UserState> = _userState
}

三、shareIn 操作符详解

基本用法

val sharedFlow: SharedFlow<Int> = flow {emit(repository.fetchData())
}.shareIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(),replay = 1
)

参数说明:

  • replay:新收集者接收的旧值数量
  • extraBufferCapacity:超出 replay 的缓冲大小
  • onBufferOverflow:缓冲策略(SUSPEND, DROP_OLDEST, DROP_LATEST

特点:

  1. 可以有多个订阅者
  2. 没有 value 属性,必须通过收集获取数据
  3. 适合事件处理(如 Toast、导航事件)

使用场景示例:

全局事件通知

class EventBus {private val _events = MutableSharedFlow<Event>()val events = _events.asSharedFlow()suspend fun postEvent(event: Event) {_events.emit(event)}// 使用 shareIn 转换外部流val externalEvents = someExternalFlow.shareIn(scope = CoroutineScope(Dispatchers.IO),started = SharingStarted.Eagerly,replay = 0)
}

四、started 参数深度解析

1. SharingStarted.Eagerly

  • 行为:立即启动,无视是否有收集者
  • 用例:需要预先缓存的数据
  • 风险:可能造成资源浪费
started = SharingStarted.Eagerly

2. SharingStarted.Lazily

  • 行为:在第一个收集者出现时启动,保持活跃直到 scope 结束
  • 用例:长期存在的共享数据
  • 注意:可能延迟首次数据获取
started = SharingStarted.Lazily

3. SharingStarted.WhileSubscribed()

  • 行为
    • 有收集者时活跃
    • 最后一个收集者消失后保持一段时间(默认 0ms)
    • 可配置 stopTimeoutMillisreplayExpirationMillis
  • 用例:大多数 UI 相关状态
// 保留5秒供可能的重新订阅
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000)

五、关键区别对比

特性stateInshareIn
返回类型StateFlowSharedFlow
初始值必须提供无要求
新收集者获取立即获得最新 value获取 replay 数量的旧值
值访问通过 .value 直接访问必须通过收集获取
典型用途UI 状态管理事件通知/数据广播
背压处理总是缓存最新值可配置缓冲策略

六、最佳实践指南

1. ViewModel 中的标准模式

class MyViewModel : ViewModel() {// 状态管理用 stateInval uiState = repository.data.stateIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(5000),initialValue = null)// 事件处理用 shareInval events = repository.events.shareIn(scope = viewModelScope,started = SharingStarted.Lazily,replay = 1)
}

2. 合理选择 started 策略

  • UI 状态WhileSubscribed(stopTimeoutMillis = 5000)
  • 配置变更需保留Lazily
  • 全局常驻数据Eagerly

3. 避免常见错误

错误1:在每次调用时创建新流

// 错误!每次调用都创建新流
fun getUser() = repository.getUserFlow().stateIn(viewModelScope, SharingStarted.Eagerly, null)// 正确:共享同一个流
private val _user = repository.getUserFlow().stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val user: StateFlow<User?> = _user

错误2:忽略 replay 配置

// 可能丢失事件
.shareIn(scope, SharingStarted.Lazily, replay = 0)// 更安全的配置
.shareIn(scope, SharingStarted.Lazily, replay = 1)

七、高级应用场景

1. 结合 Room 数据库

@Dao
interface UserDao {@Query("SELECT * FROM user")fun observeUsers(): Flow<List<User>>
}// ViewModel 中
val users = userDao.observeUsers().stateIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(),initialValue = emptyList())

2. 实现自动刷新功能

val autoRefreshData = flow {while(true) {emit(repository.fetchLatest())delay(30_000) // 每30秒刷新}
}.shareIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(),replay = 1
)

3. 多源数据合并

val combinedData = combine(repo1.data.shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 1),repo2.data.shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 1)
) { data1, data2 ->data1 + data2
}.stateIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(),initialValue = emptyList()
)

八、性能优化技巧

  1. 合理设置 replay

    • UI 状态:replay = 1(确保新订阅者立即获得状态)
    • 事件通知:replay = 0(避免重复处理旧事件)
  2. 使用 WhileSubscribed 的过期策略

    started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000,replayExpirationMillis = 60_000 // 1分钟后丢弃缓存
    )
    
  3. 避免过度缓冲

    .shareIn(scope = ...,replay = 1,extraBufferCapacity = 1, // 总共缓冲2个值onBufferOverflow = BufferOverflow.DROP_OLDEST
    )
    

九、测试策略

1. 测试 StateFlow

@Test
fun testStateFlow() = runTest {val testScope = TestScope()val flow = flowOf(1, 2, 3)val stateFlow = flow.stateIn(scope = testScope,started = SharingStarted.Eagerly,initialValue = 0)assertEquals(0, stateFlow.value) // 初始值testScope.advanceUntilIdle()assertEquals(3, stateFlow.value) // 最后发射的值
}

2. 测试 SharedFlow

@Test
fun testSharedFlow() = runTest {val testScope = TestScope()val flow = flowOf("A", "B", "C")val sharedFlow = flow.shareIn(scope = testScope,started = SharingStarted.Eagerly,replay = 1)val results = mutableListOf<String>()val job = launch {sharedFlow.collect { results.add(it) }}testScope.advanceUntilIdle()assertEquals(listOf("A", "B", "C"), results)job.cancel()
}

十、总结决策树

何时使用 stateIn

  • 需要表示当前状态(有 .value 属性)
  • UI 需要立即访问最新值
  • 适合:页面状态、表单数据、加载状态

何时使用 shareIn

  • 处理一次性事件
  • 需要自定义缓冲策略
  • 适合:Toast 消息、导航事件、广播通知

选择哪种 started 策略?

  • WhileSubscribed():大多数 UI 场景
  • Lazily:配置变更需保留数据
  • Eagerly:需要预加载的全局数据

通过本教程,应该已经掌握了 shareInstateIn 的核心用法和高级技巧。正确使用这两个操作符可以显著提升应用的性能和资源利用率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/79555.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WebRTC 服务器之SRS服务器概述和环境搭建

1.概述 SRS&#xff08;Simple Realtime Server&#xff09;是一款高性能、跨平台的流媒体服务器&#xff0c;支持多种协议&#xff0c;包括 RTMP、WebRTC、HLS、HTTP-FLV、SRT、MPEG-DASH 和 GB28181。本文介绍了 SRS&#xff0c;包括其用途、关键功能、架构和支持协议。SRS 旨…

Dify - Embedding Rerank

注意&#xff1a;v100显卡会出现不适配&#xff0c;不推荐使用 1. 安装 Docker ubuntu 22.04 docker 安装&使用_ubuntu22.04 安装docker-CSDN博客 2. 安装vllm pip install -U xformers torch torchvision torchaudio triton --index-url https://download.pytorch.org/w…

LeetCode:链表的中间结点

1、题目描述 给你单链表的头结点 head &#xff0c;请你找出并返回链表的中间结点。 如果有两个中间结点&#xff0c;则返回第二个中间结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[3,4,5] 解释&#xff1a;链表只有一个中间结点&#xff…

LabVIEW温控系统热敏电阻滞后问题

在 LabVIEW 构建的温控系统中&#xff0c;热敏电阻因热时间常数大&#xff08;2 秒左右&#xff09;产生的滞后效应&#xff0c;致使控温出现超调与波动。在不更换传感器的前提下&#xff0c;可从算法优化、硬件调整和系统设计等维度着手解决。 ​ 一、算法优化​ 1. 改进 PI…

技术犯规计入个人犯规吗·棒球1号位

在棒球运动中&#xff0c;虽然没有“技术犯规”这一特定术语&#xff0c;但存在多种违规行为或违反规则的情况&#xff0c;通常会导致判罚或处罚。以下是常见的违规行为及相关规则&#xff1a; 1. 投手违规&#xff08;Balk&#xff09; 定义&#xff1a;投手在垒上有跑垒员时…

Python核心技巧 类与实例:面向对象编程的基石

、核心概念图解 &#x1f3af; 类 vs 实例 类&#xff1a;对象的蓝图&#xff08;如"汽车设计图"&#xff09; 实例&#xff1a;类的具体实现&#xff08;如"你的特斯拉Model 3"&#xff09; class MyClass: # 类声明 count 0 # 类…

协程补充---viewModelScope 相关知识点

viewModelScope.launch 默认在 Dispatchers.Default 线程池执行Dispatchers.Default 是一个后台线程池&#xff0c;专门用于 CPU 密集型任务如果需要在主线程执行&#xff0c;必须显式指定 Dispatchers.Main remember 是 Compose 的状态管理函数(queueMenus) 是依赖项&#xff…

linux stm32mp157 GIC-V2 中断处理过程分析

/* ** 中断触发时&#xff0c;调用的 handle_arch_irq 入口地址。 ** 因为此时&#xff0c;挂接的就是 gic_handle_irq 函数&#xff01;gic_handle_irq 是个全局函数指针&#xff0c; ** static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs) ** 它是Lin…

动态指令参数:根据组件状态调整指令行为

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》、《前端求职突破计划》 &#x1f35a; 蓝桥云课签约作者、…

直方图比较

目录 1、直方图比较的概念 2、直方图比较的主要原因 3、典型应用场景 4、基础直方图比较 5、多通道直方图比较 6、实时直方图检测 1、直方图比较的概念 直方图比较是通过数学方法计算两个直方图之间的相似度或差异度的技术。在计算机视觉中&#xff0c;直方图是对图像特征…

Windows11 VS code 安装 Cline 调用 Github MCP 配置过程坑点汇总

背景 为了调研 MCP 在 windows 上如何使用本地的命令执行一些操作而实现自动化的过程&#xff0c;在 B 站视频的指导下&#xff0c;进行相应填坑过程&#xff0c;最终运行起来&#xff0c;并实现 github 自动化编程并提交代码的过程。 B 站 Cline 视频演示 Cline Cline 是一…

kdump详解

kdump 是 Linux 系统中的一种内核崩溃转储机制&#xff0c;用于在系统崩溃时将内存中的数据保存到磁盘上&#xff0c;以便后续分析系统崩溃的原因。以下是对 kdump 的详细介绍&#xff1a; 1、工作原理 kdump 利用了 Linux 系统中的双启动机制。当系统启动时&#xff0c;它会…

RGB三原色

本文来源 &#xff1a; 腾讯元宝 ​​RGB三原色&#xff08;红绿蓝&#xff09;详解​​ RGB&#xff08;Red, Green, Blue&#xff09;是光学的三原色&#xff0c;通过不同比例的混合可以产生人眼可见的绝大多数颜色。它是现代显示技术&#xff08;如屏幕、投影仪&#xff09…

CSS兼容性:挑战与策略

CSS兼容性&#xff1a;挑战与策略 引言 在前端开发的广阔领域中&#xff0c;跨浏览器兼容性无疑是最棘手且难以预测的挑战之一。当我们精心设计的网页在Chrome中完美呈现&#xff0c;却在Safari中布局崩溃&#xff0c;或在Firefox中交互失效时&#xff0c;这种挫折感是每位前…

[ 设计模式 ] | 单例模式

单例模式是什么&#xff1f;哪两种模式&#xff1f; 单例模式就是一个类型的对象&#xff0c;只有一个&#xff0c;比如说搜索引擎中的索引部分&#xff0c;360安全卫士的桌面悬浮球。 饿汉模式和懒汉模式&#xff1a;饿汉模式是线程安全的&#xff0c;懒汉模式不是线程安全的…

Notebook.ai 开源程序是一套工具,供作家、游戏设计师和角色扮演者创建宏伟的宇宙 - 以及其中的一切

​一、软件介绍 文末提供程序和源码下载 Notebook.ai 开源程序是一套工具&#xff0c;供作家、游戏设计师和角色扮演者创建宏伟的宇宙 - 以及其中的一切。 二、软件特点 Notebook 是作家的规划工具&#xff0c;用于创建从宇宙到角色、情节到单个项目的任何内容。通过浏览器、…

centos7.0无法安装php8.2/8.3

在centos安装php8.2报错 configure: error: *** A compiler with support for C17 language features is required. 配置过程检测到你的系统编译器不支持 C17 语言特性&#xff0c;而 PHP 8.2 的编译需要编译器支持 C17 sudo yum update -y sudo yum install centos-releas…

Three.js + React 实战系列 - 客户评价区细解教程 Clients 组件✨(回答式评价 + 评分星级)

对个人主页设计和实现感兴趣的朋友可以订阅我的专栏哦&#xff01;&#xff01;谢谢大家&#xff01;&#xff01;&#xff01; 在这篇博客中&#xff0c;我们将实现一个简洁的 Hear from My Clients 客户评价区域。这个区块在个人主页中可以突显用户体验和专业度&#xff0c;帮…

Vim 命令从头学习记录

学习链接&#xff1a;eleon-vim基础教程 Vim - 基础翻屏操作 光标移动&#xff1a;hjkl 20j 向下移动20行&#xff0c;w 向后移动一个字符&#xff0c;b 向前移动一个字符。 Ctrl u 向上翻半页 UP Ctrl d 向下翻半页 Down Ctrl f 向下翻整页 Forward Ctrl b 向上翻整页 …

Linux系统编程--基础指令(!!详细讲解+知识拓展)

第一讲 基础指令 ​ 我们现如今自己使用的电脑大部分是用的都是windows或者macOS&#xff0c;并配合上由微软和苹果开发的图形化界面&#xff0c;所以使用鼠标再屏幕上进行点击即可完成许多任务。但是作为操作系统的学习者&#xff0c;在linux的基础上不再使用图形化界进行操作…