• Kotlin协程:StateFlow的设计与使用


    一.StateFlow的设计

        StateFlow是一种单数据更新的热流,通过emit方法更新StateFlow的数据,通过value属性可以获取当前的数据。在StateFlow中,核心接口的继承关系如下图所示:
    在这里插入图片描述

    1.StateFlow接口

        StateFlow接口继承自SharedFlow接口,代码如下:

    public interface StateFlow<out T> : SharedFlow<T> {
        // 当前的数据
        public val value: T
    }
    
    • 订阅过程:在StateFlow中,每个FlowCollecter类型的对象都被称为订阅者。调用StateFlow类型对象的collect方法会触发订阅。正常情况下,订阅不会自动结束,但订阅者可以取消订阅,当订阅者所在的协程被取消时,订阅过程就会取消。

    • 冷流转换热流:对于一个冷流,可以通过调用stateIn方法,转换为一个单数据更新的热流。

    • 相等判定:在StateFlow中,通过Any#equals方法来判断前后两个数据是否相等。当前后两个数据相等时,数据不会被更新,订阅者也不会处理。

    • 数据缓存:StateFlow必须要有一个初始值。当新订阅者出现时,StateFlow会将最新的数据发射给订阅者。StateFlow只保留最后发射的数据,除此之外不会缓存任何其他的数据。同时,StateFlow不支持resetReplayCache方法。

    • StateFlow并发: StateFlow中所有的方法都是线程安全的,并且可以在多协程并发的场景中使用且不必额外加锁。

    • 操作符使用:对StateFlow使用flowOn操作符、conflate操作符、参数为CONFLATED或RENDEZVOUS的buffer操作符、cancellable操作符是无效的。

    • 使用场景:使用StateFlow作为数据模型,可以表示任何状态。

    • StateFlow与SharedFlow的区别:StateFlow是SharedFlow的一种特定方向的、高性能的、高效的实现,广泛的用于单状态变化的场景,所有与SharedFlow相关基本规则、约束、操作符都适用于StateFlow。当使用如下的参数创建SharedFlow对象,并对其使用distinctUntilChanged操作符,可以得到一个与StateFlow行为相同的SharedFlow对象:

    // StateFlow
    val stateFlow = MutableStateFlow(initialValue)
    
    // 与StateFlow行为相同的SharedFlow
    // 注意参数
    val sharedFlow = MutableSharedFlow(
                replay = 1,
                extraBufferCapacity = 0, 
                onBufferOverflow = BufferOverflow.DROP_OLDEST)
    
    // 设置初始值
    sharedFlow.tryEmit(initialValue)
    
    // distinctUntilChanged方法,只有当前后发射的两个数据不同时才会将数据向下游发射
    val state = sharedFlow.distinctUntilChanged()
    
    • StateFlow与ConflatedBroadcastChannel的区别:从概念上讲,StateFlow与ConflatedBroadcastChannel很相似,但二者也有很大的差别,推荐使用StateFlow,StateFlow设计的目的就是要在未来替代ConflatedBroadcastChannel:
      • StateFlow更简单,不需要实现一堆与Channel相关的接口。
      • StateFlow始终持有一个数据,并且无论在任何时间都可以安全的通过value属性获取。
      • StateFlow清楚地划分了只读的StateFlow和可读可写的StateFlow。
      • StateFlow对前后数据的比较是与distinctUntilChanged操作符类似的,而ConflatedBroadcastChannel对数据进行相等比较是基于标识引用。
      • StateFlow不能关闭,也不能表示失败,因此如果需要,所有的错误与完成信号都应该具体化。

    2. MutableStateFlow接口

        MutableStateFlow接口继承自MutableSharedFlow接口与StateFlow接口,并在此基础上定义了一个新方法compareAndSet,代码如下:

    public interface MutableStateFlow<T> : StateFlow<T>, MutableSharedFlow<T> {
        // 当前数据
        public override var value: T
    
        // 通过CAS的方式,更新value
        // 如果except与value相等,则将value更新为update,并返回true
        // 如果except与value不相等,不做任何操作,直接返回false
        // 如果except、value、update同时相等,不做任何操作,直接返回true
        public fun compareAndSet(expect: T, update: T): Boolean
    }
    

    二.StateFlow的使用

    1.MutableStateFlow方法

        在协程中,可以通过调用MutableStateFlow方法创建一个MutableStateFlow接口指向的对象,代码如下:

    public fun <T> MutableStateFlow(value: T): MutableStateFlow<T> {
        ...
    }
    

        通过MutableStateFlow方法可以创建一个类型为MutableStateFlow的对象,需要提供一个参数value,作为初始值。

        在并发场景下调用emit方法时,会使StateFlow的数据快速更新,对于处理数据慢的订阅者,将会跳过这些快速更新的数据,但当订阅者需要处理数据时,获取的一定是最新更新的数据。

    2.使用示例

        代码如下:

    private suspend fun test() {
        // 创建一个热流,初始值为1
        val flow = MutableStateFlow(1)
    
        // 将MutableStateFlow对象转换为StateFlow对象
        // StateFlow对象不能调用emit方法,因此只能用于接收
        val onlyReadFlow = flow.asStateFlow()
    
        // 接收者1
        // 启动一个新的协程
        GlobalScope.launch {
            // 触发并处理接收的数据
            onlyReadFlow.collect {
                Log.d("liduozuishuai", "test1: $it")
            }
        }
    
        // 接收者2
        // 启动一个新协程
        GlobalScope.launch {
            // 订阅监听,当collect方法触发订阅时,会首先会调onSubscription方法
            onlyReadFlow.onSubscription {
                Log.d("liduozuishuai", "test2: ")
                // 发射数据:2
                // 向下游发射数据:2,其他接收者收不到
                emit(2)
            }.onEach {
                // 处理接收的数据
                Log.d("liduozuishuai", "test2: $it")
            }.collect()
        }
    
        // 发送数据:3,多次发送
        GlobalScope.launch {
            flow.emit(3)
            flow.emit(3)
            flow.compareAndSet(3, 3)
        }
    }
    

        对于上面的示例,接收者1会依次打印出:1、3,接收者2会依次打印出2、3。接收者2由于在处理onSubscription方法发射的数据2时,MutableStateFlow对象内部的数据1变成了数据3,因此在处理完数据2后,直接处理数据3。

  • 相关阅读:
    6、流程控制语句
    C语言宏定义提供了一些进阶操作
    pycharm中做web应用(11)基于Django和mysql 做用户登录验证
    简述分布式链路追踪工具——Jaeger
    基于php+mysql的菜品食谱美食网
    Mybatis-Plus【吐血详解 一篇掌握】
    10.8c++作业
    QImage
    解决高并发问题
    Monaco Editor编辑器
  • 原文地址:https://blog.csdn.net/LeeDuoZuiShuai/article/details/127038757