Kotlin flow 详解

flow是kotlin提供的异步数据流api,可以按照顺序依次发送多个值,写法和RxJava很像。

flow分成两种类型,冷流和热流:

  • 冷流:通过flow方法创建的流。
  • 热流:SharedFlow和StateFlow

冷流

使用方法

kotlin 复制代码
val flow = flow {
    Log.e("TAG", "flow: start")
    emit(1)
    delay(100)
    emit(2)
    delay(100)
    emit(3)
}
flow.collect {
    Log.e("TAG", "collect: $it")
}

使用flow方法创建一个SafeFlow的对象,然后调用他的collect方法,就会依次收到emit发送的数据。

原理

kotlin 复制代码
/**
 * 在上面的例子中
 * block = {
 *     Log.e("TAG", "flow: start")
 *     emit(1)
 *     delay(100)
 *     emit(2)
 *     delay(100)
 *     emit(3)
 * }
 */
public fun <T> flow(@BuilderInference block: suspend FlowCollector<T>.() -> Unit): Flow<T> = SafeFlow(block)

private class SafeFlow<T>(private val block: suspend FlowCollector<T>.() -> Unit) : AbstractFlow<T>() {
     override suspend fun collectSafely(collector: FlowCollector<T>) {
        /**
         * collector = {
         *     Log.e("TAG", "collect: $it")
         * }
         */
        collector.block()
    }
}

冷流的实现原理还是比较简单的,flow函数会创建一个SafeFlow的对象,将block参数传入到SafeFlow中。当调用SafeFlow的collect方法时,会调用到collectSafely。在collectSafely中就直接调用了block函数对象。collect方法也需要传入一个collector: FlowCollector<T>参数,就是collector调用了block,然后再block函数对象中又调用了collector.emit发送数据。

简化一点就是collect调用了block,block中调用了emit又会调回来到collector中。

所以冷流的emit会在collect之后才会执行,因为是collect中调用的emit。

热流

SharedFlow简单介绍

kotlin 复制代码
val flow = MutableSharedFlow<Int>()

// 接收
launch {
    flow.collect {
        println(it)
    }
}

// 发送
launch {
    flow.emit(1)
    flow.emit(2)
}

可以看到创建SharedFlow时,没有像创建冷流flow一样传入一个block的函数参数。自然调用collect也没有可以重新运行的代码。

SharedFlow原理

kotlin 复制代码
public fun <T> MutableSharedFlow(
    replay: Int = 0,
    extraBufferCapacity: Int = 0,
    onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
): MutableSharedFlow<T> {

    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)
}

SharedFlow的实现类是SharedFlowImpl,collect方法和冷流的实现不一样。

kotlin 复制代码
override suspend fun collect(collector: FlowCollector<T>): Nothing {
    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)
    }
}

sharedFlow的collect有两层循环,内层循环在不断的获取新值,如果获取到新值将会跳出内层循环,然后给消费者发送新值,之后有开始循环,一直监听下去。

tryTakeValue最终会调用到getPeekedValueLockedAt从buffer中获取数据,buffer是一个数组。emit发送的数据都保存在这个数组中。

kotlin 复制代码
private fun getPeekedValueLockedAt(index: Long): Any? =
    when (val item = buffer!!.getBufferAt(index)) {
        is Emitter -> item.value
        else -> item
    }

tryEmit不会挂起发送方,流程要简单些,先从tryEmit说起。

kotlin 复制代码
override fun tryEmit(value: T): Boolean {
    var resumes: Array<Continuation<Unit>?> = EMPTY_RESUMES
    val emitted = synchronized(this) {
        // 保存value到buffer中
        if (tryEmitLocked(value)) {
            resumes = findSlotsToResumeLocked(resumes)
            true
        } else {
            false
        }
    }
    for (cont in resumes) cont?.resume(Unit)
    return emitted
}

tryEmitLocked会调用到enqueueLocked保存value到buffer数组中。

kotlin 复制代码
private fun enqueueLocked(item: Any?) {
    val curSize = totalSize
    val buffer = when (val curBuffer = buffer) {
        null -> growBuffer(null, 0, 2)
        else -> if (curSize >= curBuffer.size) growBuffer(curBuffer, curSize,curBuffer.size * 2) else curBuffer
    }
    // 保存value
    buffer.setBufferAt(head + curSize, item)
}

SharedFlow的emit方法会先调用tryEmit,tryEmit返回false才会调用<font style="color:#bcbec4;background-color:#191a1c;">emitSuspen</font>

kotlin 复制代码
override suspend fun emit(value: T) {
    if (tryEmit(value)) return // fast-path
    emitSuspend(value)
}

emitSuspend是通过suspendCancellableCoroutine来实现挂起的,创建了一个Emitter对象添加到了SharedFlow的buffer数组中。当collect执行时,会调用到updateCollectorIndexLocked,最后拿到Continuation来恢复协程。

kotlin 复制代码
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
        Emitter(this, head + totalSize, value, cont).also {
            // 添加Emitter对象到buffer数组中
            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)
}

StateFlow原理

StateFlow的使用和SharedFlow很相似,这里就不重复说了。直接说StateFlow的实现原理。SharedFlow是用一个buffer数组来保存数据的,StateFlow是用value变量来保存的。

StateFlowImpl是SateFlow的实现类,StateFlowImpl实现了MutableStateFlow接口,MutableStateFlow中的value就是用来记录SateFlow的值。

kotlin 复制代码
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
}

SateFlow的emit和tryEmit都是直接赋值。

kotlin 复制代码
// StateFlowImpl
override fun tryEmit(value: T): Boolean {
    this.value = value
    return true
}

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

如果多线程emit数据就存在线程安全问题,所以flow也提供了多线程更新数据的方法(update)

kotlin 复制代码
public inline fun <T> MutableStateFlow<T>.update(function: (T) -> T) {
    while (true) {
        val prevValue = value
        val nextValue = function(prevValue)
        if (compareAndSet(prevValue, nextValue)) {
            return
        }
    }
}
相关推荐
程序猿乐锅1 小时前
万字详解Single-flight(单飞模式)
java·开发语言·分布式·后端·学习·single-flight
灿飞3 小时前
Kotlin与Java虚拟机六:基础与类型系统:一切皆对象,但对象有代价
java·kotlin
橙光成长3 小时前
android-ad-cleaner:android免 Root 批量关闭系统开屏与营销推送
android·1024程序员节
深圳雨林凯AI3 小时前
图案裂变的底色衰减:alpha通道、亮度对比与白墨分色的量化拆解
android
揽星河..3 小时前
【C语言】指针
c语言·开发语言
mmsx3 小时前
Android 模块化收口:能力组合框架、手写 LifecycleOwner 与热修复共存
android
小小数媒成员3 小时前
UGUI原理(一)
开发语言·游戏·unity·游戏引擎
速易达网络4 小时前
Three.js 动画入门教学:从场景搭建到流畅动效
开发语言·javascript·ecmascript
福大大架构师每日一题4 小时前
excelize v2.11.1发布:Go 1.26.0起步,16项安全修复与嵌入对象能力全面升级
开发语言·安全·golang