Kotlin协程:StateFlow的设计与使用

Kotlin协程:StateFlow的设计与使用

一.StateFlow的设计

StateFlow是一种单数据更新的暖流,经过emit办法更新StateFlow的数据,经过value属性能够获取当时的数据。在StateFlow中,中心接口的承继联系如下图所示:

Kotlin协程: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。