关于kotlin中的flow(二)

7,395 阅读9分钟

[上一章]关于kotlin中的flow(一)

在上一章中,主要讲了Flow和MutableFlow的应用,这一章主要讲StateFlow以及SharedFlow。的原理以及运用。

SharedFlow和stateFLow和普通flow最大的区别就是,他们是热流,是热流,是热流,重要的事情说三遍。先来看下区别:

  • 冷流:只有当订阅者发起订阅时,事件的发送者才会开始发送事件。
  • 热流:不管订阅者是否存在,flow本身可以调用emit(或者tryEmit)发送事件,可以有多个观察者,也可在需要的时候发送事件。 从描述看,SharedFlow更接近于传统的观察者模式。

然后我们再来看一下sharedflow和stateflow的关系:

本质上,stateFlow就是一种特殊的sharedflow。

具体原因我会慢慢分析,先看一下sharedflow的源码和定义:

/**
 * Creates a [MutableSharedFlow] with the given configuration parameters.
 *
 * This function throws [IllegalArgumentException] on unsupported values of parameters or combinations thereof.
 *
 * @param replay the number of values replayed to new subscribers (cannot be negative, defaults to zero).
 * @param extraBufferCapacity the number of values buffered in addition to `replay`.
 *   [emit][MutableSharedFlow.emit] does not suspend while there is a buffer space remaining (optional, cannot be negative, defaults to zero).
 * @param onBufferOverflow configures an [emit][MutableSharedFlow.emit] action on buffer overflow. Optional, defaults to
 *   [suspending][BufferOverflow.SUSPEND] attempts to emit a value.
 *   Values other than [BufferOverflow.SUSPEND] are supported only when `replay > 0` or `extraBufferCapacity > 0`.
 *   **Buffer overflow can happen only when there is at least one subscriber that is not ready to accept
 *   the new value.** In the absence of subscribers only the most recent [replay] values are stored and
 *   the buffer overflow behavior is never triggered and has no effect.
 */
@Suppress("FunctionName", "UNCHECKED_CAST")
public fun <T> MutableSharedFlow(
    replay: Int = 0,
    extraBufferCapacity: Int = 0,
    onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
): MutableSharedFlow<T> {
    require(replay >= 0) { "replay cannot be negative, but was $replay" }
    require(extraBufferCapacity >= 0) { "extraBufferCapacity cannot be negative, but was $extraBufferCapacity" }
    require(replay > 0 || extraBufferCapacity > 0 || onBufferOverflow == BufferOverflow.SUSPEND) {
        "replay or extraBufferCapacity must be positive with non-default onBufferOverflow strategy $onBufferOverflow"
    }
    val bufferCapacity0 = replay + extraBufferCapacity
    val bufferCapacity = if (bufferCapacity0 < 0) Int.MAX_VALUE else bufferCapacity0 // coerce to MAX_VALUE on overflow
    return SharedFlowImpl(replay, bufferCapacity, onBufferOverflow)
}

这是一个生成MutableShardFlow的顶层函数,有三个参数:

  • reply:事件粘滞数,既当事件发送在订阅者订阅之前,会将订阅之前的第 i 份数据发送给这个新的订阅者。(类似的liveData粘滞事件就相当于reply=1,不同的是sharedflow可以自定义粘滞的数量)默认值=0,代表没有粘滞

  • extraBufferCapacity:缓存容量,既然存在粘滞,就说明shardflow是有缓存的,缓存一方面用于粘滞事件的发送,另一方面主要是为了处理响应流中常见的背压问题,既当下游的订阅者collector消费速度低于上游生产速度(比如订阅者被挂起),数据流会被放在缓存中,缓存的大小就是由这个参数控制。

  • onBufferOverflow:由背压就有处理策略,sharedflow默认为suspend,也即是如果当事件数量超过缓存,发送就会被挂起,其他还有drop_oldest和drop_latest

这是一份关于缓存的图解:

/*
    Logical structure of the buffer

              buffered values
         /-----------------------\
                      replayCache      queued emitters
                      /----------/----------------------\
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |   | 1 | 2 | 3 | 4 | 5 | 6 | E | E | E | E | E | E |   |   |   |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
           ^           ^           ^                      ^
           |           |           |                      |
          head         |      head + bufferSize     head + totalSize
           |           |           |
 index of the slowest  |    index of the fastest
  possible collector   |     possible collector
           |           |
           |     replayIndex == new collector's index
           ---------------------- /
      range of possible minCollectorIndex

      head == minOf(minCollectorIndex, replayIndex) // by definition
      totalSize == bufferSize + queueSize // by definition

   INVARIANTS:
      minCollectorIndex = activeSlots.minOf { it.index } ?: (head + bufferSize)
      replayIndex <= head + bufferSize
 */

我们先不看SharedFlowImpl的具体实现,先来看一下MutableSharedFlow接口的定义:

public interface MutableSharedFlow<T> : SharedFlow<T>, FlowCollector<T> {
    override suspend fun emit(value: T)
    
    public fun tryEmit(value: T): Boolean
    
    public val subscriptionCount: StateFlow<Int>
    
    @ExperimentalCoroutinesApi
    public fun resetReplayCache()
}


public interface SharedFlow<out T> : Flow<T> {
    /**
     * A snapshot of the replay cache.
     */
    public val replayCache: List<T>
}


public interface FlowCollector<in T> {

    /**
     * Collects the value emitted by the upstream.
     * This method is not thread-safe and should not be invoked concurrently.
     */
    public suspend fun emit(value: T)
}

由内到外开始分析:

  • FlowCollector: 是flow的统一接口,就是收集数据用的。
  • SharedFlow :有一个参数replayCache,看名字就是到了这个就是事件的粘滞列表(可以通过MutableSharedFlow.resetReplyCache来重置这个列表)。再来看一下说明(这个说明其实就是对整个ShardFlow的说明):
/**
 * A _hot_ [Flow] that shares emitted values among all its collectors in a broadcast fashion, so that all collectors
 * get all emitted values. A shared flow is called _hot_ because its active instance exists independently of the
 * presence of collectors. This is opposed to a regular [Flow], such as defined by the [`flow { ... }`][flow] function,
 * which is _cold_ and is started separately for each collector.
 *
 * **Shared flow never completes**. A call to [Flow.collect] on a shared flow never completes normally, and
 * neither does a coroutine started by the [Flow.launchIn] function. An active collector of a shared flow is called a _subscriber_.
 *
 * A subscriber of a shared flow can be cancelled. This usually happens when the scope in which the coroutine is running
 * is cancelled. A subscriber to a shared flow is always [cancellable][Flow.cancellable], and checks for
 * cancellation before each emission. Note that most terminal operators like [Flow.toList] would also not complete,
 * when applied to a shared flow, but flow-truncating operators like [Flow.take] and [Flow.takeWhile] can be used on a
 * shared flow to turn it into a completing one.
 *
 * A [mutable shared flow][MutableSharedFlow] is created using the [MutableSharedFlow(...)] constructor function.
 * Its state can be updated by [emitting][MutableSharedFlow.emit] values to it and performing other operations.
 * See the [MutableSharedFlow] documentation for details.
 *
 * [SharedFlow] is useful for broadcasting events that happen inside an application to subscribers that can come and go.
 * For example, the following class encapsulates an event bus that distributes events to all subscribers

提取出几个关键点:

  • sharedflow 是热流,并且给所有的订阅者(collector)发送数据,sharedFlow独立于所有的订阅者。
  • 对shardflow调用flow.collect方法永远不会complete,这一点非常重要,如果忽视了这一点,非常容易导致内存溢出。
  • sharedflow的订阅者可以取消任务,一般是伴随着协程作用域的取消而取消。
  • 一些flow的操作符通用永远无法complete,比如flow.tolist。但是有一些可以完成比如flow.take/flow.takewhile
  • sharedflow对于应用内事件的订阅者很有用,比较推荐用sharedflow来实现eventbus的功能。

看完sharedFlow接口,接着看mutableSharedFlow接口和其中的方法:

  • emit:对sharedflow发送一条数据,如果buffer满了同时采用了suspend策略,emit方法会被挂起。
  • tryEmit:非挂起函数,如果发送成功了,返回true,如果失败则返回false。注意:当策略不采用suspend时,tryEmit永远返回true。(也就是说,只有当采用emit会被挂起的时候,采用tryEmit会返回false)
  • subscriptionCount 当前flow活动中的订阅者数量。(这个参数本身是一个stateFlow),官方还给我们提供了一个监听第一个订阅者出现以及最后一个订阅者消失的例子:
* sharedFlow.subscriptionCount
*     .map { count -> count > 0 } // map count into active/inactive flag
*     .distinctUntilChanged() // only react to true<->false changes
*     .onEach { isActive -> // configure an action
*         if (isActive) onActive() else onInactive()//onActive() onInactive()
*     }
*     .launchIn(scope) // launch it
  • resetReplayCache 重置粘滞缓存,新的订阅者只能获取新的事件,但是旧的订阅者(old subscriber)仍然会从缓存中获取旧的事件(因为缓存列表本身分为粘滞区和缓存区)

看完mutablesharedflow的接口定义,下面来看它的具体实现SharedFlowImpl。

SharedFlowImpl

/*
    Logical structure of the buffer

              buffered values
         /-----------------------\
                      replayCache      queued emitters
                      /----------/----------------------\
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |   | 1 | 2 | 3 | 4 | 5 | 6 | E | E | E | E | E | E |   |   |   |
     +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
           ^           ^           ^                      ^
           |           |           |                      |
          head         |      head + bufferSize     head + totalSize
           |           |           |
 index of the slowest  |    index of the fastest
  possible collector   |     possible collector
           |           |
           |     replayIndex == new collector's index
           ---------------------- /
      range of possible minCollectorIndex

      head == minOf(minCollectorIndex, replayIndex) // by definition
      totalSize == bufferSize + queueSize // by definition

   INVARIANTS:
      minCollectorIndex = activeSlots.minOf { it.index } ?: (head + bufferSize)
      replayIndex <= head + bufferSize
 */
 
private class SharedFlowImpl<T>(
    private val replay: Int,
    private val bufferCapacity: Int,
    private val onBufferOverflow: BufferOverflow
) : AbstractSharedFlow<SharedFlowSlot>(), MutableSharedFlow<T>, CancellableFlow<T>, FusibleFlow<T> {

// Stored state
private var buffer: Array<Any?>? = null // allocated when needed, allocated size always power of two
private var replayIndex = 0L // minimal index from which new collector gets values
private var minCollectorIndex = 0L // minimal index of active collectors, equal to replayIndex if there are none
private var bufferSize = 0 // number of buffered values
private var queueSize = 0 // number of queued emitters

// Computed state
private val head: Long get() = minOf(minCollectorIndex, replayIndex)
private val replaySize: Int get() = (head + bufferSize - replayIndex).toInt()
private val totalSize: Int get() = bufferSize + queueSize
private val bufferEndIndex: Long get() = head + bufferSize
private val queueEndIndex: Long get() = head + bufferSize + queueSize


//重写的这个方法,关键就是replySize,而这个其实根据上图就是需要粘滞的数量的坐标值,既最后一位的缓存坐标-粘滞数量
override val replayCache: List<T>
    get() = synchronized(this) {
        val replaySize = this.replaySize
        if (replaySize == 0) return emptyList()
        val result = ArrayList<T>(replaySize)
        val buffer = buffer!! // must be allocated, because replaySize > 0
        @Suppress("UNCHECKED_CAST")
        for (i in 0 until replaySize) result += buffer.getBufferAt(replayIndex + i) as T
        result
    }


//collect方法重点在于while(true),说明这个方法是会不停阻塞协程的,具体使用时,一个协程内第一个collect会阻塞接下来的代码,所以需要做异步处理
//会先尝试非阻塞获取,获取不到则使用挂起方式调用
override suspend fun collect(collector: FlowCollector<T>) {
    val slot = allocateSlot()
    try {
        if (collector is SubscribedFlowCollector) collector.onSubscription()
        val collectorJob = currentCoroutineContext()[Job]
        while (true) {
            var newValue: Any?
            while (true) {
                newValue = tryTakeValue(slot) // attempt no-suspend fast path first
                if (newValue !== NO_VALUE) break
                awaitValue(slot) // await signal that the new value is available
            }
            collectorJob?.ensureActive()
            collector.emit(newValue as T)
        }
    } finally {
        freeSlot(slot)
    }
}

//tryEmit主要是根据tryEmitLocked的结果返回
override fun tryEmit(value: T): Boolean {
    var resumes: Array<Continuation<Unit>?> = EMPTY_RESUMES
    val emitted = synchronized(this) {
        if (tryEmitLocked(value)) {
            resumes = findSlotsToResumeLocked(resumes)
            true
        } else {
            false
        }
    }
    for (cont in resumes) cont?.resume(Unit)
    return emitted
}


//这里可以看到,当策略采用suspended同时缓存溢出的时候,返回false,否则,永远返回true,同时做一些事件上的入队处理等
@Suppress("UNCHECKED_CAST")
private fun tryEmitLocked(value: T): Boolean {
    // Fast path without collectors -> no buffering
    if (nCollectors == 0) return tryEmitNoCollectorsLocked(value) // always returns true
    // With collectors we'll have to buffer
    // cannot emit now if buffer is full & blocked by slow collectors
    if (bufferSize >= bufferCapacity && minCollectorIndex <= replayIndex) {
        when (onBufferOverflow) {
        ////////////这里是返回结果的地方/////////////
            BufferOverflow.SUSPEND -> return false // will suspend
            BufferOverflow.DROP_LATEST -> return true // just drop incoming
            BufferOverflow.DROP_OLDEST -> {} // force enqueue & drop oldest instead
        }
    }
    enqueueLocked(value)
    bufferSize++ // value was added to buffer
    // drop oldest from the buffer if it became more than bufferCapacity
    if (bufferSize > bufferCapacity) dropOldestLocked()
    // keep replaySize not larger that needed
    if (replaySize > replay) { // increment replayIndex by one
        updateBufferLocked(replayIndex + 1, minCollectorIndex, bufferEndIndex, queueEndIndex)
    }
    return true
}

//会先进行一次tryEmit的处理,当返回false的时候再进行suspend的发送操作
override suspend fun emit(value: T) {
    if (tryEmit(value)) return // fast-path
    emitSuspend(value)
}


//可以看到,在内部再一次调用了tryEmitLocked()的方法,来再次确认队列是否满了
//emit方法可以理解成先使用tryEmit进行发送,如果发送失败,则将emitter加入到队列中
private suspend fun emitSuspend(value: T) = suspendCancellableCoroutine<Unit> sc@{ cont ->
    var resumes: Array<Continuation<Unit>?> = EMPTY_RESUMES
    val emitter = synchronized(this) lock@{
        // recheck buffer under lock again (make sure it is really full)
        if (tryEmitLocked(value)) {
            cont.resume(Unit)
            resumes = findSlotsToResumeLocked(resumes)
            return@lock null
        }
        // add suspended emitter to the buffer
        Emitter(this, head + totalSize, value, cont).also {
        ////////////////加入队列//////////////////
            enqueueLocked(it)
            queueSize++ // added to queue of waiting emitters
            // synchronous shared flow might rendezvous with waiting emitter
            if (bufferCapacity == 0) resumes = findSlotsToResumeLocked(resumes)
        }
    }
    // outside of the lock: register dispose on cancellation
    emitter?.let { cont.disposeOnCancellation(it) }
    // outside of the lock: resume slots if needed
    for (r in resumes) r?.resume(Unit)
}

....

}

sharedFlowImp几个实现的方法和原理就在这里,下面说一下StateFlow。

StateFlow

stateFlow其实就是一种特殊的SharedFlow:

public interface StateFlow<out T> : SharedFlow<T> {
    /**
     * The current value of this state flow.
     */
    public val value: T
}
public interface MutableStateFlow<T> : StateFlow<T>, MutableSharedFlow<T> {
    /**
     * The current value of this state flow.
     *
     * Setting a value that is [equal][Any.equals] to the previous one does nothing.
     *
     * This property is **thread-safe** and can be safely updated from concurrent coroutines without
     * external synchronization.
     */
    public override var value: T

    /**
     * Atomically compares the current [value] with [expect] and sets it to [update] if it is equal to [expect].
     * The result is `true` if the [value] was set to [update] and `false` otherwise.
     *
     * This function use a regular comparison using [Any.equals]. If both [expect] and [update] are equal to the
     * current [value], this function returns `true`, but it does not actually change the reference that is
     * stored in the [value].
     *
     * This method is **thread-safe** and can be safely invoked from concurrent coroutines without
     * external synchronization.
     */
    public fun compareAndSet(expect: T, update: T): Boolean
}
private class StateFlowImpl<T>(
    initialState: Any // T | NULL
) : AbstractSharedFlow<StateFlowSlot>(), MutableStateFlow<T>, CancellableFlow<T>, FusibleFlow<T>

在sharedFlow的基础上加入了一个value(类似liveData),新增了一个初始值,增加一个防数据抖动的判断函数(更新数值的时候比较当前值,如果相同则不更新)

override val replayCache: List<T>
    get() = listOf(value)

override fun tryEmit(value: T): Boolean {
    this.value = value
    return true
}

override suspend fun emit(value: T) {
    this.value = value
}

@Suppress("UNCHECKED_CAST")
override fun resetReplayCache() {
    throw UnsupportedOperationException("MutableStateFlow.resetReplayCache is not supported")
}

从这里可以看到,stateFlow就是一个replaySize=1的sahredFlow,同时不能调用它的resetRecycleCache方法否则会报错。

SharedFlow和StateFlow的侧重点

  • StateFlow就是一个replaySize=1的sharedFlow,同时它必须有一个初始值,此外,每次更新数据都会和旧数据做一次比较,只有不同时候才会更新数值。
  • StateFlow重点在状态,ui永远有状态,所以StateFlow必须有初始值,同时对ui而言,过期的状态毫无意义,所以stateFLow永远更新最新的数据(和liveData相似),所以必须有粘滞度=1的粘滞事件,让ui状态保持到最新。具体应用时,StateFlow适合那些长期保持某种状态的ui,比如一些开关值之类。
  • SharedFlow侧重在事件,当某个事件触发,发送到队列之中,按照挂起或者非挂起、缓存策略等将事件发送到接受方,在具体使用时,SharedFlow更适合通知ui界面的一些事件,比如toast等,也适合作为viewModel和repository之间的桥梁用作数据的传输。

将冷流转化为热流

使用官方提供的shareIn方法:

val myFlow = flow<String> { }.shareIn(
    scope = viewModelScope,//作用域
    started = SharingStarted.WhileSubscribed(),//启动策略
    replay = 0//粘滞度
)
  • SharingStarted.WhileSubscribed():存在订阅者时,将使上游提供方保持活跃状态。
  • SharingStarted.Eagerly:立即启动提供方。
  • SharingStarted.Lazily:在第一个订阅者出现后开始共享数据,并使数据流永远保持活跃状态。

将StateFlow转化为livedata

val myFlow = MutableStateFlow<String>("value")
val myLiveData = mViewModel.myFlow.asLiveData()

感谢大家收看,希望大家点个赞👍

下一章关于kotlin中的flow(三)-使用flow来实现eventbus