Flow 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. 节省资源:避免重复计算(多个界面可以共享同一个数据源)

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

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

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

六、生活场景类比

场景1:微信群(stateIn)
  • 群里最后一条消息就是当前状态(.value)
  • 新成员进群立刻能看到最后一条消息
  • 适合:工作群的状态同步
场景2:电台广播(shareIn)
    • 主播不断发送新消息
    • 听众打开收音机时:
      • 可以设置是否听之前的回放(replay)
      • 但无法直接问"刚才最后一首歌是什么"(无.value)
    • 适合:交通路况实时播报

七、什么时候用哪个?

用 stateIn 当:
  • 需要随时知道"当前值"
  • 数据会持续变化且需要被多个地方使用
  • 例如:
    • 用户登录状态
    • 购物车商品数量
    • 实时位置更新
用 shareIn 当:
  • 只关心新事件,不关心历史值
  • 事件可能被多个接收者处理
  • 例如:
    • "订单支付成功"通知
    • 错误提示消息
    • 页面跳转指令

八、超简单选择流程图

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

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

二 Kotlin Flow 的 shareIn 和 stateIn 操作符完全指南

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

一、核心概念解析

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:缓冲策略(SUSPENDDROP_OLDESTDROP_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)
    • 可配置 stopTimeoutMillis 和 replayExpirationMillis
  • 用例:大多数 UI 相关状态
// 保留5秒供可能的重新订阅
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000)

五、关键区别对比

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

六、最佳实践指南

1. ViewModel 中的标准模式
class MyViewModel : ViewModel() {
    // 状态管理用 stateIn
    val uiState = repository.data
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = null
        )
    
    // 事件处理用 shareIn
    val 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. 测试 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:需要预加载的全局数据

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


————————————————
版权声明:本文为CSDN博主「stevenzqzq」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_26296197/article/details/147661573

内容概要:本文详细介绍了一个基于MATLAB实现的SWT-SVM故障诊断分类预测项目,通过平稳小波变换(SWT)进行信号去噪与多尺度特征提取,结合支持向量机(SVM)实现机械设备故障的智能分类。项目涵盖从数据采集、预处理、SWT分解、特征提取与降维(如PCA)、模型训练与优化(含交叉验证、网格搜索、贝叶斯优化)、性能评估(混淆矩阵、ROC曲线、F1分数等)到结果可视化与GUI界面开发的完整流程。系统具备高可解释性、强鲁棒性良好工程集成能力,适用于多行业设备健康监测,并提供完整的代码实现与部署方案。; 适合人群:具备一定MATLAB编程基础,熟悉信号处理与机器学习算法的高校研究生、科研人员及工业领域从事设备故障诊断、智能运维的工程师技术人员。; 使用场景及目标:①应用于智能制造、风电、轨道交通、石化、航空航天等领域的设备故障早期检测与健康状态评估;②构建端到端的智能诊断pipeline,提升诊断准确率与自动化水平;③通过GUI交互界面实现数据导入、模型训练、实时预测与结果导出,服务于科研教学与工业实际部署。; 阅读建议:建议读者结合文中提供的完整MATLAB代码与GUI设计,逐步复现各模块功能,重点关注SWT参数选择、特征降维策略、SVM超参数优化及模型评估方法。在实践过程中调试信号处理流程与分类性能,深入理解算法原理与工程落地的关键环节。
### Flow 的概念及用法 Flow 是 Kotlin 协程库的一部分,提供了一种声明式的异步数据流处理机制。它能够高效地处理一系列的数据项,并支持背压(backpressure),这使得它可以很好地应对高频率产生的事件或者大量数据的传输场景[^1]。 #### 创建使用 Flow 创建一个简单的 Flow 可以通过 `flow` 构建器函数完成: ```kotlin val numbers = flow { for (i in 1..5) { emit(i) // 发射单个值给收集者 delay(100L) // 模拟耗时操作 } } ``` 要消费这个 Flow 中的数据,则可以调用其 `collect()` 方法: ```kotlin numbers.collect { value -> println(value) } // 输出:1, 2, 3, 4, 5 (每条消息之间有短暂延迟) ``` 为了优化性能并减少重复订阅的成本,还可以利用像 `stateIn`, `shareIn` 这样的运算符将冷流转换成热流[^2]。 ### Trigger 的概念及其在不同上下文下的含义 Trigger 在不同的编程框架技术栈中有多种解释。通常来说,触发器指的是当满足特定条件或发生某些预定义事件时执行的动作或逻辑单元。例如,在数据库管理系统中,Triggers 被用来响应表上的插入、更新或删除操作;而在前端 Web 开发里,它们可能关联着用户的交互行为如点击按钮等。 对于 Salesforce 平台而言,Before-save Triggers 特指一种特殊的流程构建组件,允许开发者编写自定义业务规则来修改记录属性或阻止保存动作的发生,从而确保数据的一致性完整性。 ### 结合实例说明两者关系 在一个典型的移动应用程序架构设计案例中,假设有一个聊天功能模块需要实时同步最新的对话列表至客户端设备上显示出来。此时就可以采用如下方案: - 使用 **Flow** 来监听服务器端推送过来的新消息通知; - 当接收到新消息后作为 **trigger** 去驱动 UI 层重新加载展示内容。 这种模式不仅简化了代码结构而且提高了系统的灵活性与扩展能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值