Flow,SharedFlow,StateFlow的使用及原理

初步认识Flow(冷流)

1.Flow上游

a.先看一下flow的简单使用

kotlin 复制代码
private fun flowTest() = runBlocking {
    flow {
        emit(0)
        emit(1)
        emit(2)
        emit(3)
        emit(4)
    }.collect {
        Log.d(TAG, "it:$it")
    }
}

输出结果:
//输出结果:
//it:0
//it:1
//it:2
//it:3
//it:4

Flow从字面意思理解就是流,Flow除了有发送方和接收方之外还可以有中转站,什么是中转站呢,中转站就是我们常用的中间操作符。比如 : .filter() .map()等等

b.中转站用法

scss 复制代码
private fun flowTest() = runBlocking {
    flow {
        //发送方
        emit(0)
        emit(1)
        emit(2)
        emit(3)
        emit(4)
    }.filter {
        //中转站(1)
        it > 2
    }.map {
        //中转站(2)
        it * 2
    }.collect {
        //接受方
        Log.d(TAG, "it:$it")
    }
}
//输出结果:
//it:6
//it:8

(1) flow{}: 是一个高阶函数,作用就是创建一个新的Flow,创建好后就要把消息发送出去,这里的emit是发射、发送的意思,那么flow{}的作用就是创建一个数据流并且将数据发送出去;

(2) filter{}、map{}: 这是中间操作符,都是高阶函数,就像中转站一样对数据进行处理后向下传递;

(3) collect{}: 终止操作符,终止Flow数据流并接收从上游传递的数据。

(4) 除了通过flow{}创建Flow之外还有flowOf{},也可以创建一个Flow

kotlin 复制代码
private fun flowOfTest() {
    runBlocking {
        flowOf(0, 1, 2, 3, 4)
            .filter {
                it > 2
            }.map {
                it * 2
            }.collect {
                Log.d(TAG, "it:$it")
            }
    }
}
//输出结果:
//it:6
//it:8

(5)collect ->是终止操作符, 作用是接收从上游传递的数据,那要是不接收会怎么样?

答案是:运行上面的代码会发现什么都没有做就结束了,而添加**collect函数后filtermap的日志就是正常输出的,因此得出一个结论:只有调用终止操作符 collect**之后,Flow 才会开始工作。所以Flow是一个 冷流

没有接受方Flow是不会工作的。

(6)上面两段代码都发送了5条数据,然后由collect接收,那么是一次发送完毕还是逐条发送呢?

kotlin 复制代码
private fun flowOfTest() {
    runBlocking {
        flowOf(0, 1, 2, 3, 4)
            .filter {
                Log.d(TAG, "filter:$it")
                it > 2
            }.map {
                Log.d(TAG, "map:$it")
                it * 2
            }.collect {
                Log.d(TAG, "collect:$it")
            }
    }
}
输出结果:
//filter:0
//filter:1
//filter:2
//filter:3
//map:3
//collect:6
//filter:4
//map:4
//collect:8

从输出结果可以很清楚的知道Flow一次只会处理一条数据。

(7)Kotlin还提供了Flow转List、List转Flow的API....Flow创建的几种方式

Flow创建方式 使用场景 用法
flow 未知数据集 flow{ emit() }.collect{ }
flowOf 已知数据集 flowOf(T).collect{ }
listOf 已知数据来源的集合 listOf(T).asFlow().collect{ }

2.Flow中转站(中间操作符)

A.中转站就是我们常用的中间操作符,跟集合一样操作符(这边只列出了一部分。更多的自己点击查看源码)

kotlin 复制代码
/**
 * 返回只包含与给定[predicate]匹配的原始流的值的流
 */
public inline fun <T> Flow<T>.filter(crossinline predicate: suspend (T) -> Boolean): Flow<T> = transform { value ->
    if (predicate(value)) return@transform emit(value)
}

/**
 * 返回只包含与给定[predicate]值不匹配的原始流的值的流
 */
public inline fun <T> Flow<T>.filterNot(crossinline predicate: suspend (T) -> Boolean): Flow<T> = transform { value ->
    if (!predicate(value)) return@transform emit(value)
}

/**
 * 返回一个只包含原始流的非空值的流
 */
public fun <T: Any> Flow<T?>.filterNotNull(): Flow<T> = transform<T?, T> { value ->
    if (value != null) return@transform emit(value)
}

/**
 * 返回一个流,其中包含对原始流的每个值应用给定[transform]函数的结果。
 */
public inline fun <T, R> Flow<T>.map(crossinline transform: suspend (value: T) -> R): Flow<R> = transform { value ->
    return@transform emit(transform(value))
}

/**
 * 返回一个流,将每个元素包装成[IndexedValue],包含value和它的索引(从0开始)。
 */
public fun <T> Flow<T>.withIndex(): Flow<IndexedValue<T>> = flow {
    var index = 0
    collect { value ->
        emit(IndexedValue(checkIndexOverflow(index++), value))
    }
}

/**
 * 返回一个流,在上游流的每个值被下游发出之前调用给定的[action]。
 */
public fun <T> Flow<T>.onEach(action: suspend (T) -> Unit): Flow<T> = transform { value ->
    action(value)
    return@transform emit(value)
}

B.Flow特有的操作符.生命周期onStart, onCompletion

kotlin 复制代码
private fun flowTest() = runBlocking {
    flow {
        //发送方
        emit(0)
        emit(1)
        emit(2)
        emit(3)
        emit(4)
    }.filter {
        //中转站(1)
        Log.d(TAG, "filter:$it")
        it > 2
    }.map {
        Log.d(TAG, "map:$it")
        //中转站(2)
        it * 2
    }.onStart {
        Log.d(TAG, "onStart:")
    }.onCompletion {
        Log.d(TAG, "onCompletion:")
    }
        .collect {
            //接受方
            Log.d(TAG, "collect:$it")
        }
}
输出:
//onStart:
//filter:0
//filter:1
//filter:2
//filter:3
//map:3
//collect:6
//filter:4
//map:4
//collect:8
//onCompletion

(1)可以看到onStart函数的执行数序与它在代码中定义的顺序没有关系,而其他两个操作符filtermap的执行流程则跟它们定义的顺序息息相关。

(2)onCompletion在它的注释中也标注的比较清楚,类似于finally,都是在最后执行。

C. Flow特有的操作符->catch异常处理

Flow中的catch异常处理时要遵循上下游规则的,因为Flow是具有上下游之分的,具体来讲就是catch只能管理自己上游发生的异常,对于它下游的异常则无能为力,用代码来展示一下他们的区别:

kotlin 复制代码
private fun flowOfTest() {
    runBlocking {
        flowOf(0, 1, 2, 3, 4)
            .filter {
                Log.d(TAG, "filter:$it")
                it > 2
            }.map {
                Log.d(TAG, "map:$it")
                it * 2
            }.map {
                it / 0
            }.catch {
                Log.d(TAG, "catch:$it")
            }.collect {
                Log.d(TAG, "collect:$it")
            }
    }
}
输出结果:
//filter:0
//filter:1
//filter:2
//filter:3
//map:3
//catch:java.lang.ArithmeticException: divide by zero

上游发生 异常 ,在异常后捕获

kotlin 复制代码
private fun flowOfTest() {
    runBlocking {
        flowOf(0, 1, 2, 3, 4)
            .filter {
                Log.d(TAG, "filter:$it")
                it > 2
            }.map {
                Log.d(TAG, "map:$it")
                it * 2
            }.catch {
                Log.d(TAG, "catch:$it")
            }.map {
                it / 0
            }.collect {
                Log.d(TAG, "collect:$it")
            }
    }
}
//直接奔溃了
// java.lang.ArithmeticException: divide by zero

上游捕获异常,下游发生 异常

(1)从两段代码可以非常清楚的总结出:上游发生异常并在异常后捕获是不会造成程序终止的,而在上游捕获异常,下游发生异常时则会造成程序终止。

(2)那么下游的异常就无法捕获了吗?并不是,对于下游的异常可以考虑采用最传统的做法try catch方法

(3)总结:Flow中的catch操作符的作用与它所在的位置是强相关的,catch无法捕获的可以采用try catch捕获。

D.Flow特有的操作符------切换Context:flowOn、launchIn

Flow因为它具有上游、中间操作符、下游的特性,使得它可以处理复杂且异步执行的任务,那么异步执行的任务中大多又涉及到线程切换,Flow也恰好提供了线程切换的API。

flowOn

scss 复制代码
private fun flowTest2() {
    //flow切换线程
    runBlocking {
        flow {
            emit(0)
            emit(1)
            emit(2)
            emit(3)
            emit(4)
        }.filter {
            Log.d(TAG, "filter:$it")
            Log.d(TAG, "thread = ${Thread.currentThread().name}")
            it > 2
        }.flowOn(Dispatchers.IO).map {
            Log.d(TAG, "map:$it")
            Log.d(TAG, "thread = ${Thread.currentThread().name}")
            it * 2
        }.collect {
            Log.d(TAG, "collect:$it")
            Log.d(TAG, "thread = ${Thread.currentThread().name}")
        }
    }
}
//输出结果
//filter:0
//thread = DefaultDispatcher-worker-2
//filter:1
//thread = DefaultDispatcher-worker-2
//filter:2
//thread = DefaultDispatcher-worker-2
//filter:3
//thread = DefaultDispatcher-worker-2
//map:3
//map thread = main
//collect: 6
//collect thread = main
//map:4
//map thread = main
//collect: 8
//collect thread = main

flowOn线程的切换范围与catch一样仅针对上游,那么要制定collect中的Context该怎么办?

可以使用withContext,如果除了collect之外还想让其他操作符也运行在collect所在的线程中就会遇到问题,虽然依旧可以使用withContext但是这样的写法就会很丑陋,就像下面这样失去了原本简洁的链式调用。那么解决这个问题的另一种方案launchIn就派上用场了。

launchIn

kotlin 复制代码
public fun <T> Flow<T>.launchIn(scope: CoroutineScope): Job = scope.launch {
    collect() 
}

使用了launchIn操作符的flow无法再调用collect,从launchIn源码中可知,launchIn调用了collect()

scss 复制代码
private fun flowTest3() {
    runBlocking {
        flow {
            emit(0)
            emit(1)
            emit(2)
            emit(3)
            emit(4)
        }.filter {
            Log.d(TAG, "filter:$it")
            Log.d(TAG, "thread = ${Thread.currentThread().name}")
            it > 2
        }.flowOn(Dispatchers.IO).map {
            Log.d(TAG, "map:$it")
            Log.d(TAG, "map thread = ${Thread.currentThread().name}")
            it * 2
        }.onEach {
            Log.d(TAG, "onEach:$it")
            Log.d(TAG, "onEach thread = ${Thread.currentThread().name}")
        }.launchIn(CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate))
        //当然你可以自定义的dispatcher
        //lifecycleScope == CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
    }
}
//输出结果:
//map{}、onEach{}、flow{}运行在主线程
//filter{}运行在DefaultDispatcher -> DefaultDispatcher-worker-1

3.Flow下游->终止操作符

A. 在Flow中最常见的操作符就是collect(),除此之外还有first()Last()single()fold{}reduce{}。还有一个特殊的当Flow调用toList转换成集合后toList后面的API都不再属于Flow因此这也就说明toList也算是一种终止操作符

4.Flow总结&特点

Flow的原理

1.Flow流程中为什么是冷流?

我们来分析下flow的源码

kotlin 复制代码
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.block()
    }
}

flow是一个高阶函数,参数类型是FlowCollector.() -> Unit,FlowCollector是它的扩展或者成员方法,没有参数也没有返回值,flow()函数的返回值是Flow,具体到返回类型是SafeFlow()SafeFlow()AbstractFlow()的子类。

kotlin 复制代码
public abstract class AbstractFlow<T> : Flow<T>, CancellableFlow<T> {
    //注释1
    public final override suspend fun collect(collector: FlowCollector<T>) {
        //注释2
        val safeCollector = SafeCollector(collector, coroutineContext)
        try {
            //注释3
            collectSafely(safeCollector)
        } finally {
            safeCollector.releaseIntercepted()
        }
    }

    public abstract suspend fun collectSafely(collector: FlowCollector<T>)
}

AbstractFlow源码中可以知道它实现了Flow接口,这里进一步知道了SafeFlow间接的实现了Flow接口。这里深入了解一下AbstractFlow做了哪些工作,通过上面三个注释进行解释:

  • 注释1:这个collect就是demo中的collect的调用。这里做一个猜想collect的调用会触发上游Lambda中的emit()函数的执行,然后将数据传递给collect

  • 注释2:这里主要就是进行了安全检查,SafeCollector只是把collect中的参数FlowCollector<T>重新进行了封装,具体的安全检查是怎么样的稍后进行分析;

  • 注释3:这里调用了collectSafely这个抽象方法,而这里的具体实现是在SafeFlow中的collectSafely,然后调用了collector.block(),这里其实就是调用了flow()中的emit方法,或者说collector.block()调用了4次emit()函数。

现在来总结一下为什么Flow是冷的:FLow之所以是冷的是因为它的构造过程,在它的构造过程中构造了一个 SafeFlow对象但并不会触发Lambda表达式的执行,只有当 collect() 被调用后Lambda表达式才开始执行所以它是冷的。

2.SafeCollector分析

kotlin 复制代码
@Suppress("CANNOT_OVERRIDE_INVISIBLE_MEMBER", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "UNCHECKED_CAST")
internal actual class SafeCollector<T> actual constructor(
    //注释1
    @JvmField internal actual val collector: FlowCollector<T>,
    @JvmField internal actual val collectContext: CoroutineContext
) : FlowCollector<T>, ContinuationImpl(NoOpContinuation, EmptyCoroutineContext), CoroutineStackFrame {

    override val callerFrame: CoroutineStackFrame? get() = completion as? CoroutineStackFrame

    override fun getStackTraceElement(): StackTraceElement? = null

    @JvmField // Note, it is non-capturing lambda, so no extra allocation during init of SafeCollector
    internal actual val collectContextSize = collectContext.fold(0) { count, _ -> count + 1 }
    private var lastEmissionContext: CoroutineContext? = null
    private var completion: Continuation<Unit>? = null

    // ContinuationImpl
    override val context: CoroutineContext
        get() = completion?.context ?: EmptyCoroutineContext

    override fun invokeSuspend(result: Result<Any?>): Any {
        result.onFailure { lastEmissionContext = DownstreamExceptionElement(it) }
        completion?.resumeWith(result as Result<Unit>)
        return COROUTINE_SUSPENDED
    }

    // Escalate visibility to manually release intercepted continuation
    public actual override fun releaseIntercepted() {
        super.releaseIntercepted()
    }

    /**
     *  这是状态机重用的巧妙实现。
     * 首先它检查它没有被并发使用(这里是明确禁止的),
     * 然后只缓存一个完成实例以避免在每次发出时进行额外分配,使其在热路径上消除垃圾。
     */
    //注释2
    override suspend fun emit(value: T) {
        return suspendCoroutineUninterceptedOrReturn sc@{ uCont ->
            try {
                //注释3
                emit(uCont, value)
            } catch (e: Throwable) {
                //保存已抛出emit异常的事实,便于被上游 
                lastEmissionContext = DownstreamExceptionElement(e)
                throw e
            }
        }
    }

    private fun emit(uCont: Continuation<Unit>, value: T): Any? {
        val currentContext = uCont.context
        currentContext.ensureActive()
        //注释4
        val previousContext = lastEmissionContext
        if (previousContext !== currentContext) {
            checkContext(currentContext, previousContext, value)
        }
        completion = uCont
        //注释5
        return emitFun(collector as FlowCollector<Any?>, value, this as Continuation<Unit>)
    }

    private fun checkContext(
        currentContext: CoroutineContext,
        previousContext: CoroutineContext?,
        value: T
    ) {
        if (previousContext is DownstreamExceptionElement) {
            exceptionTransparencyViolated(previousContext, value)
        }
        checkContext(currentContext)
        lastEmissionContext = currentContext
    }

    private fun exceptionTransparencyViolated(exception: DownstreamExceptionElement, value: Any?) {
        error("""
            Flow exception transparency is violated:
                Previous 'emit' call has thrown exception ${exception.e}, but then emission attempt of value '$value' has been detected.
                Emissions from 'catch' blocks are prohibited in order to avoid unspecified behaviour, 'Flow.catch' operator can be used instead.
                For a more detailed explanation, please refer to Flow documentation.
            """.trimIndent())
    }
}

//注释6
private val emitFun =
    FlowCollector<Any?>::emit as Function3<FlowCollector<Any?>, Any?, Continuation<Unit>, Any?>

    

注释1:这里的collect参数对应的就是Flow在构造时的匿名内部类FlowCollector,在AS中看到的this:FlowCollector<Int>就是它;

注释2:这个emit()函数其实就是demo中用来发送数据的emit(0)...emit(3),这里可以理解为Flow上游发送的数据最终会传递到这个emit()中;

注释3:这里的emit(uCont, value)函数中多了两个参数,其中第一个是suspendCoroutineUninterceptedOrReturn,它是一个高阶函数,是把挂起函数的Continuation暴露出来并作为参数进行传递,第二个则是上游发送过来的数据。这里还有一个异常捕获,异常被捕获后存储在lastEmissionContext,作用是:在下游发送异常以后可以让上游感知到。下面会有一个对比;

注释4:这里对当前协程上下文与之前的协程上下文进行对比,如果两者不一致就会执行checkContext(),在它里面做出进一步的判断和提示;

注释5:这里调用的就是下游的emit(),也就是FlowCollector中的emit函数,这里接收的那个value就是上游的emit(0)...emit(3)传递过来的,可以理解成下面demo中的代码:

kotlin 复制代码
private fun flowOtherTest() {
    runBlocking {
        flow {
            emit(0)
            emit(1)
            emit(2)
            emit(3)
        }.collect(object : FlowCollector<Int> {
            //注释5调用的就是这个emit方法。
            override suspend fun emit(value: Int) {
                Log.d(TAG, "collect:$value")
            }
        })
    }
}

注释6:这是函数引用的语法,代表了它就是FlowCollectoremit()方法

3.FlowCollector 上游和下游之间的链接

kotlin 复制代码
public interface Flow<out T> {
    public suspend fun collect(collector: FlowCollector<T>)
}

public fun interface FlowCollector<in T> {
    public suspend fun emit(value: T)
}

Flow提供了一个方法,让下游触发一个收集动作collect,理所当然下游作为收集者的FlowCollector也提供了一个emit方法,让下游来接收上游发送数据emit()

在Flow的构造过程中构造了SafeFlow对象并且间接的实现了Flow,Flow中的collect就是终止操作,而collect函数中的参数FlowCollector中的emit()函数则是下游用来接收上游发送数据的emit()

这里再来回顾一下SafeCollector方法中所做的事情:首先collect终止符的调用会触发上游Lambda中的emit()函数执行,它将数据发送出去,然后进入到SafeCollector中的emit()函数,在这个函数中又将从上游数据发送到下游collect中的emitFun()函数中,这样就完成了连接。所以说 FlowCollector是上下游之间的桥梁。(也可以理解为回调)

4.总结

1.Flow的调用过程分为三个步骤:

  • 上游的Flow创建SafeFlow对象,下游的Flow的collect()函数触发上游的emit()函数执行开始发送数据;
  • 上游的emit()发送的数据进入到SafeCollector,其实上游的emit()函数调用的就是SafeCollector中的emit()函数;
  • SafeCollector中调用emitFun()其实就是调用了下游的emit()函数将数据传递给下游。

2.中间操作符:

如果有中间操作符的话,每个操作符都会有上游和下游,并且都是被下游触发执行,也会触发自己的上游,同时还会接收来自上游的数据并传递给自己的下游;

ShardFlow与StateFlow(热流)

上面我们介绍到Flow是冷流。那么如果我们要使用热流,该怎么办呢?

答案是Flow 有提供相关实现: 那就是StateFlow 和 SharedFlow 。

StateFlow 和 SharedFlow 是热流,生产数据不依赖消费者消费,热流与消费者是一对多的关系,当有多个消费者时,它们之间的数据都是同一份。

1.SharedFlow与StateFlow的特点

A.SharedFlow
  • 对于同一个数据流,可以允许有多个订阅者共享。
  • 不调用collect收集数据,也会开始发送数据。
  • 允许缓存历史数据(默认是非粘性,当设置缓存数据后即可实现为粘性, replay = 1)
  • 发送数据函数都是线程安全的。
  • 不防抖。可以发送相同的值
  • 无初始化值
B.StateFlow
  • 都允许多个消费者
  • 都有只读与可变类型
  • 永远只保存一个状态值,会把最新值重现给订阅者,即粘性。
  • 防抖。设置重复的值不会重新发送给订阅者
  • 必须传入初始值,保证值的空安全,永远有值
C. StateFlow与LiveData不同的是
  • 强制要求初始默认值
  • 支持CAS模式赋值
  • 默认支持防抖 过滤
  • value的空安全校验
  • Flow丰富的异步数据流操作
  • 默认没有Lifecycle支持,flowcollect是挂起函数,会一直等待数据流传递数据
  • 线程安全LiveDatapostValue虽然也可在异步使用,但会导致数据丢失。

LiveData除了对于Lifecycle的支持,StateFlow基本都是处于优势

2.SharedFlow创建及使用

kotlin 复制代码
public fun <T> MutableSharedFlow(
    //1.缓存的历史数据容量
    replay: Int = 0,
    //2.除历史数据外的额外缓存区容量
    extraBufferCapacity: Int = 0,
    //3.背压策略
    onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
): MutableSharedFlow<T> {
    ...
    val bufferCapacity0 = replay + extraBufferCapacity
    val bufferCapacity = if (bufferCapacity0 < 0) Int.MAX_VALUE else bufferCapacity0
    return SharedFlowImpl(replay, bufferCapacity, onBufferOverflow)
}

public interface MutableSharedFlow<T> : SharedFlow<T>, FlowCollector<T> {
    //4.线程安全的挂机函数发送数据
    override suspend fun emit(value: T)
    //5.线程安全的尝试发送数据
    public fun tryEmit(value: T): Boolean
    //6.共享数据流的订阅者数量
    public val subscriptionCount: StateFlow<Int>
    ...
}

注释1 replay 表示历史元素缓存区容量。

  • 能够将最新的数据缓存到集合内,当历史缓存区满后,会移除最早的元素。
  • 当在新消费者订阅了该数据流,会先将历史缓存区元素依次发送给新的消费者,然后才发送新元素。

注释2:extraBufferCapacity:MutableSharedFlow 缓存的数据个数为 replay + extraBufferCapacity; 缓存一方面用于粘性事件的发送,另一方面也为了处理背压问题,既下游的消费者的消费速度低于上游生产者的生产速度时,数据会被放在缓存中。。

注释3:onBufferOverflow:背压处理策略,缓存区满后怎么处理(挂起或丢弃数据),默认挂起。注意,当没有订阅者时,只有最近 replay 个数的数据会存入缓存区,不会触发 onBufferOverflow 策略。

kotlin 复制代码
    private fun sharedFlow(){
        runBlocking {
            val _sharedFlow = MutableSharedFlow<Int>()
            val sharedFlow = _sharedFlow.asSharedFlow()
            lifecycleScope.launch(Dispatchers.IO) {
                for(i in 0..50){
                    Log.d(TAG, "emit:$i")
                    _sharedFlow.emit(i)
                    delay(50)
                }
            }
        }
    }
    输出结果:
    //emit:0
    //emit:1
    // ...
    //emit:50

虽然此时并没有消费者订阅,但依旧会执行发送数据操作,只是目前没有设置历史缓存,所有数据都被"抛弃"了。

再让我们看看设置replay = 3的情况。这时候订阅者会收到3个值.

kotlin 复制代码
private fun sharedFlow(){
    runBlocking {
        val _sharedFlow = MutableSharedFlow<Int>(replay = 3)
        val sharedFlow = _sharedFlow.asSharedFlow()
        lifecycleScope.launch(Dispatchers.IO) {
            for(i in 0..50){
                Log.d(TAG, "emit:$i")
                _sharedFlow.emit(i)
                delay(50)
            }
        }

            delay(5000)
            //延迟5000毫秒去订阅,其实这时候流已经发送完了.相当于新的订阅者
            sharedFlow.onEach {
                Log.d(TAG, "onEach:$it")
            }.launchIn(lifecycleScope)
    }
}
输出结果:
//emit:0
//emit:1
// ...
//emit:50
//onEach:48
//onEach:49
//onEach:50

Flow冷流转换成SharedFlow热流

kotlin 复制代码
private fun shareIn(){
    runBlocking {
        flowOf(0,1,2,3,4).shareIn(lifecycleScope, SharingStarted.WhileSubscribed())
    }
}

public fun <T> Flow<T>.shareIn(
    scope: CoroutineScope,
    started: SharingStarted,
    replay: Int = 0
): SharedFlow<T> {
    val config = configureSharing(replay)
    val shared = MutableSharedFlow<T>(
        replay = replay,
        extraBufferCapacity = config.extraBufferCapacity,
        onBufferOverflow = config.onBufferOverflow
    )
    @Suppress("UNCHECKED_CAST")
    val job = scope.launchSharing(config.context, config.upstream, shared, started, NO_VALUE as T)
    return ReadonlySharedFlow(shared, job)
}

这里started表示新创建的共享数据流的启动与停止策略。

  • Eagerly->立即开始发送数据源。并且消费端永远收集数据,只会收到历史缓存和后续新数据,直到所在协程取消。
  • Lazily->a.等待出现第一个消费者订阅后,才开始发送数据源。保证第一个消费者能收到所有数据,但后续消费者只能收到历史缓存和后续数据。b.消费者会永远等待收集数据,直到所在协程取消
  • WhileSubscribed->a.可以说是Lazily策略的进阶版,同样是等待第一个消费者订阅后,才开始发送数据源。

b.但其可以配置在最后一个订阅者关闭后,共享数据流上游停止的时间(默认为立即停止),与历史数据缓存清 空时间(默认为永远保留)。

需要注意,在使用shareIn每次都会创建一个新 SharedFlow 实例 ,并且该实例会一直保留在 内存 ,直到被垃圾回收。所以最好减少转换流的执行次数,不要在函数内每次都调用这类函数。

3.SharedFlow原理

通过MutableSharedFlow工厂函数创建的SharedFlow,内部实际是创建了SharedFlowImpl对象,是使用数组缓存所有数据。

kotlin 复制代码
internal open class SharedFlowImpl<T>(
    private val replay: Int, // replayCache的最大容量
    private val bufferCapacity: Int, // buffered values的最大容量
    private val onBufferOverflow: BufferOverflow // 溢出策略
) : AbstractSharedFlow<SharedFlowSlot>(), MutableSharedFlow<T>, CancellableFlow<T>, FusibleFlow<T> {

    // 缓存数组,用于保存emit方法发射的数据,在需要时进行初始化
    private var buffer: Array<Any?>? = null
    // 新的订阅者从replayCache中获取数据的起始位置
    private var replayIndex = 0L
    // 当前所有的订阅者从缓存数组中获取的数据中,对应位置最小的索引
    // 如果没有订阅者,则minCollectorIndex的值等于replayIndex
    private var minCollectorIndex = 0L
    // 缓存数组中buffered values缓存数据的数量
    private var bufferSize = 0
    // 缓存数组中queued emitters缓存数据的数量
    private var queueSize = 0

    // 当前缓存数组的起始位置
    private val head: Long get() = minOf(minCollectorIndex, replayIndex)
    // 当前缓存数组中replayCache缓存数据的数量
    private val replaySize: Int get() = (head + bufferSize - replayIndex).toInt()
    // 当前缓存数组中已经缓存的数据的数量
    private val totalSize: Int get() = bufferSize + queueSize
    // 当前缓存数组中buffered values的最末尾位置索引的后一位
    private val bufferEndIndex: Long get() = head + bufferSize
    // 当前数组中queued emitters的最末尾位置索引的后一位
    private val queueEndIndex: Long get() = head + bufferSize + queueSize

    ...
   
   //2.tryEmit 
   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
    }
    //1.emit
     override suspend fun emit(value: T) {
            if (tryEmit(value)) return // fast-path 先尝试发送数据
            emitSuspend(value)  //无法发送数据时先创建挂起函数
     }
}
发送数据

SharedFlowImpl这个类第一次看起来有点多,这里先从emittryEmit看起,看看是如何实现发送数据。

tryEmit内部通过synchronized加锁,是线程安全的。

注释1:emit方法会先进行一次tryEmit的处理,当返回false的时候再进行suspend的发送操作

注释2:通过前面对tryEmit方法的注释判断缓存策略为 BufferOverflow.SUSPEND才有可能为true,所以再看tryEmitLocked方法

kotlin 复制代码
//从这里可以看到,当策略采用suspended同时缓存溢出的时候,返回false,否则,永远返回true,同时做一些事件上的入队处理等
@Suppress("UNCHECKED_CAST")
private fun tryEmitLocked(value: T): Boolean {
    //注释1.--------没有检测到订阅者(collect收集器)
    if (nCollectors == 0) return tryEmitNoCollectorsLocked(value) // always returns true
    
    //注释2.--------检测到订阅者(collect收集器)如果当前有订阅者,同时buffered values已达到最大容量。
    if (bufferSize >= bufferCapacity && minCollectorIndex <= replayIndex) {
        when (onBufferOverflow) {
            BufferOverflow.SUSPEND -> return false // will suspend,只有这里才会返回true
            BufferOverflow.DROP_LATEST -> return true // just drop incoming
            BufferOverflow.DROP_OLDEST -> {} // force enqueue & drop oldest instead
        }
    }
    
    //注释3.--------buffered values还可以继续添加数据, 将数据加入到缓存数组中
    // 这里因为tryEmit方法不会挂起emit方法所在的协程, 
    // 所以value没有被封装成Emitter类型的对象
    enqueueLocked(value)
    
    bufferSize++ // value was added to buffer
    
    // drop oldest from the buffer if it became more than bufferCapacity
    //注释4.--------  如果buffered values的数据数量超过最大容量的限制, 
    //则调用dropOldestLocked方法,丢弃最旧的数据
    if (bufferSize > bufferCapacity) dropOldestLocked()
    
    // keep replaySize not larger that needed
    //注释5.--------   如果replayCache中数据的数量超过了最大容量
    if (replaySize > replay) { // increment replayIndex by one
        // 更新replayIndex的值,replayIndex向前移动一位
        updateBufferLocked(replayIndex + 1, minCollectorIndex, bufferEndIndex, queueEndIndex)
    }
    return true
}
  • tryEmitLocked方法代码注释1处可知: 因为是热流tryEmitNoCollectorsLocked方法就是没有订阅者的情况
kotlin 复制代码
private fun tryEmitNoCollectorsLocked(value: T): Boolean {
    assert { nCollectors == 0 }
    //replay == 0直接返回,不需要去缓存
    if (replay == 0) return true // no need to replay, just forget it now
    //将值入队
    enqueueLocked(value) // enqueue to replayCache
    //缓存加1
    bufferSize++ // value was added to buffer
    // drop oldest from the buffer if it became more than replay
    //如果缓存满了,就删除最老的那一条数据
    // 如果buffered values的数据数量超过了replayCache的最大容量 
    // 则丢弃最旧的数据 
    // 因为新订阅者只会从replayCache中取数据, 
    // 如果没有订阅者,buffered values的数据数量超过replayCache的最大容量没有意义
    if (bufferSize > replay) dropOldestLocked()
    //重新设置订阅者的值位置索引
    minCollectorIndex = head + bufferSize // a default value (max allowed)
    return true
}


// enqueues item to buffer array, caller shall increment either bufferSize or queueSize
private fun enqueueLocked(item: Any?) {
    //totalSize = 缓冲值的数目+排队发射器的数量
    val curSize = totalSize
    val buffer = when (val curBuffer = buffer) {
        // 缓存数组为空,则进行初始化,初始化容量为2
        null -> growBuffer(null, 0, 2)
        // 如果超过了当前缓存数组的最大容量,则进行扩容,新的缓存数组的容量为之前的2倍 
        // growBuffer方法会把原来缓存数组的数据填充到新的缓存数组中
        else -> if (curSize >= curBuffer.size) growBuffer(curBuffer, curSize,curBuffer.size * 2) else curBuffer
    }
    buffer.setBufferAt(head + curSize, item)
}

从tryEmitLocked方法代码注释2处可知现在是有订阅者的情况:

1.bufferSize >= bufferCapacity 缓存数组中buffered values缓存数据的数量 >= buffered values的最大容量。

2.minCollectorIndex <= replayIndex 当前所有的订阅者从缓存数组中获取的数据中,对应位置最小的索引<=新的订阅者从replayCache中获取数据的起始位置

到此tryEmitLocked方法分析完了。回到tryEmit方法

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

findSlotsToResumeLocked

kotlin 复制代码
private fun findSlotsToResumeLocked(resumesIn: Array<Continuation<Unit>?>): Array<Continuation<Unit>?> {
    // 引用参数中的续体数组
    var resumes: Array<Continuation<Unit>?> = resumesIn
    // 用于记录需要恢复的续体的数量
    var resumeCount = resumesIn.size
    // 遍历订阅者数组
    forEachSlotLocked loop@{ slot ->
        // 获取续体,如果续体为空,说明对应订阅者的协程没有挂起,本次循环返回
        val cont = slot.cont ?: return@loop
        // 判断slot中index是否符合要求
        // 如果不符合要求,则本次循环返回
        if (tryPeekLocked(slot) < 0) return@loop
        // 如果需要恢复的续体的数量超过续体数组的容量,则进行扩容
        // 新的续体数组的容量是之前续体数组容量的2倍
        if (resumeCount >= resumes.size) resumes = resumes.copyOf(maxOf(2, 2 * resumes.size))
        // 保存续体到续体数组中
        resumes[resumeCount++] = cont
        // 清空slot中保存的续体
        slot.cont = null
    }
    // 返回收集完的续体数组
    return resumes
}

到这里非 挂起 的发送流程结束,下面看看挂起发送流程

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

挂起调用emitSuspend方法,这里也调用了上面提到的findSlotsToResumeLocked方法

kotlin 复制代码
private suspend fun emitSuspend(value: T) = 
  // 直接挂起emit方法所在的协程,获取续体
  suspendCancellableCoroutine<Unit> sc@{ cont ->
    var resumes: Array<Continuation<Unit>?> = EMPTY_RESUMES
    // 加锁
    val emitter = synchronized(this) lock@{
        // 这里再次尝试以tryEmit的方式发射数据
        if (tryEmitLocked(value)) {
            // 如果发射成功,则恢复续体的执行
            cont.resume(Unit)
            // 收集已经挂起的订阅者的续体
            resumes = findSlotsToResumeLocked(resumes)
            // 返回
            return@lock null
        }
        // 将续体、待发射的数据等封装成Emitter类型的对象
        Emitter(this, head + totalSize, value, cont).also {
            // 加入到缓存数组中
            enqueueLocked(it)
            // queued emitters的数据的数量加1
            queueSize++
            // 如果buffered values的最大容量为0,即不存在
            // 则收集已经挂起的订阅者的续体,保存到局部变量resumes中
            if (bufferCapacity == 0) resumes = findSlotsToResumeLocked(resumes)
        }
    }
    // emitter对象监听emit方法所在协程的取消
    // 发生取消时会调用emitter对象的dispose方法
    emitter?.let { cont.disposeOnCancellation(it) }
    // 遍历,唤起挂起的订阅者
    for (cont in resumes) cont?.resume(Unit)
}

sharedFlow设置了缓存优先从replayCache取数据.SharedFlowImpl类实现了SharedFlow接口,重写了其中的常量replayCache,当有新订阅者出现时,如果replayCache存在,并且有缓存数据,则优先从replayCache中获取,代码如下:

kotlin 复制代码
override val replayCache: List<T>
    // 只能获取,不能设置,加锁
    get() = synchronized(this) {
        // 获取当前replayCache中缓存数据的数量
        val replaySize = this.replaySize
        // 如果数量为0,则返回一个空列表
        if (replaySize == 0) return emptyList()
        // 若数量不为0,则根据容量创建一个列表
        val result = ArrayList<T>(replaySize)
        // 获取缓存数组
        val buffer = buffer!!
        // 遍历replayCache,将数据进行类型转换,并添加到列表中
        @Suppress("UNCHECKED_CAST")
        for (i in 0 until replaySize) result += buffer.getBufferAt(replayIndex + i) as T
        // 返回列表
        result
    }
collect流程(接受数据)
kotlin 复制代码
SharedFlowImpl.kt
@Suppress("UNCHECKED_CAST")
override suspend fun collect(collector: FlowCollector<T>) {
    // 为当前的订阅者分配一个SharedFlowSlot类型的对象
    val slot = allocateSlot()
    try {
        // 如果collector类型为SubscribedFlowCollector,
        // 说明订阅者监听了订阅过程的启动,则先回调
        if (collector is SubscribedFlowCollector) collector.onSubscription()
        // 获取订阅者所在的协程
        val collectorJob = currentCoroutineContext()[Job]
        // 死循环
        while (true) {
            var newValue: Any?
            // 死循环
            while (true) {
                // 从缓存数组中获取数据
                newValue = tryTakeValue(slot)
                // 如果获取数据成功,则跳出循环
                if (newValue !== NO_VALUE) break
                // 走到这里,说明获取数据失败,
                // 挂起订阅者所在协程,等待新数据的到来
                awaitValue(slot)
            }
            // 走到这里,说明已经获取到了数据
            // 判断订阅者所在协程是否是存活的,如果不是则抛出异常
            collectorJob?.ensureActive()
            // 进行类型转换,并向下游发射数据
            collector.emit(newValue as T)
        }
    } finally {
        // 释放已分配的SharedFlowSlot类型的对象
        freeSlot(slot)
    }
}


@SharedImmutable
@JvmField
internal val NO_VALUE = Symbol("NO_VALUE")

在collect方法中,通过tryTakeValue方法获取数据,代码如下:

kotlin 复制代码
private fun tryTakeValue(slot: SharedFlowSlot): Any? {
    var resumes: Array<Continuation<Unit>?> = EMPTY_RESUMES
    // 加锁
    val value = synchronized(this) {
        // 从slot中获取index
        // index表示当前应该从缓存数组的index位置中获取数据
        val index = tryPeekLocked(slot)
        // 如果index小于0,说明没有数据
        if (index < 0) {
            // 返回空数据标识
            NO_VALUE
        } else { // 如果有数据
            // 获取当前的slot的index
            val oldIndex = slot.index
            // 从缓存数组的index处获取数据
            val newValue = getPeekedValueLockedAt(index)
            // 计算下一次获取数据的位置,并保存到slot中
            slot.index = index + 1
            // 更新缓存数组的位置,并获取缓存数组与订阅者数组中可恢复的续体
            resumes = updateCollectorIndexLocked(oldIndex)
            // 返回获取的数据
            newValue
        }
    }
    // 遍历,恢复续体
    for (resume in resumes) resume?.resume(Unit)
    // 返回获取的数据
    return value
}

@JvmField
@SharedImmutable
internal val EMPTY_RESUMES = arrayOfNulls<Continuation<Unit>?>(0)

在tryTakeValue方法,获取数据之前,首先会调用tryPeekLocked方法,判断数据所在的位置是否符合要求,代码如下:

kotlin 复制代码
private fun tryPeekLocked(slot: SharedFlowSlot): Long {
    // 从slot中获取index
    val index = slot.index
    // 如果是在buffered values中获取,则直接返回
    if (index < bufferEndIndex) return index
    
    // 走到这里说明是要在queued emitters中获取,
    // 如果buffered values的最大容量大于0,则返回-1
    // 在buffered values可以存在的情况下,禁止发射者和订阅者接触
    if (bufferCapacity > 0) return -1L
    
    // 走到这里说明要在queued emitters中获取,同时buffered values的最大容量为0
    // 这种情况缓存数组只能有queued emitters,
    // 因此,只能处理queued emitters中的第一个Emitter类型的对象
    // 如果当前订阅者想要处理下一个Emitter类型的对象,则返回-1
    if (index > head) return -1L
    
    // 走到这里说明要在queued emitters中获取,同时buffered values的最大容量为0
    // 并且要获取当前的正在处理的Emmiter类型的对象
    // 如果queued emitters为空,说明当前没有Emmiter类型的对象,则返回-1
    if (queueSize == 0) return -1L
    // 满足上述要求,返回index
    return index
}

如果数据所在的位置符合要求,则会调用getPeekedValueLockedAt方法获取数据,代码如下:

kotlin 复制代码
private fun getPeekedValueLockedAt(index: Long): Any? =
    // 从缓存数组中index位置获取数据
    when (val item = buffer!!.getBufferAt(index)) {
        // 如果是Emitter类型的,则进行拆箱,获取数据
        is Emitter -> item.value
        // 直接返回
        else -> item
    }

Emitter类是SharedFlowImpl类的内部类,用于在挂起调用emit方法所在的协程后,对emit方法发射的数据及挂起后的续体进行封装,代码如下:

less 复制代码
private class Emitter(
    @JvmField val flow: SharedFlowImpl<*>,
    @JvmField var index: Long, // 当前对象在缓存数组中的位置
    @JvmField val value: Any?,// emit方法发射的数据
    @JvmField val cont: Continuation<Unit> // 挂起的续体
) : DisposableHandle {
    override fun dispose() = flow.cancelEmitter(this)
}

在collect方法中,当订阅者无数据可获取时,则会调用awaitValue方法,挂起订阅者所在的协程,代码如下:

kotlin 复制代码
private suspend fun awaitValue(slot: SharedFlowSlot): Unit = 
  // 直接挂起订阅者所在的协程
  suspendCancellableCoroutine { cont ->
    // 加锁
    synchronized(this) lock@{
        // 再次检查当前的index是否满足要求
        val index = tryPeekLocked(slot)
        // 如果确实不满足要求
        if (index < 0) {
            // 保存续体到slot中
            slot.cont = cont
        } else { // 如果再次检查发现index这时满足要求
            // 则恢复挂起,并返回
            cont.resume(Unit)
            return@lock
        }
        // 保存续体到slot中
        slot.cont = cont
    }
}

到此SharedFlow就分析完成了。

我们知道SharedFlow内部是基于数组+synchronized,订阅分发的逻辑则是轮询取出内部缓存数组的数据,没有数据则将订阅挂起,直到上游再次发送数据

StateFlow创建及使用

作为SharedFlow的子类,StateFlow在使用上与其父类基本相同。

kotlin 复制代码
public fun <T> MutableStateFlow(value: T): MutableStateFlow<T> = StateFlowImpl(value ?: NULL)

public interface MutableStateFlow<T> : StateFlow<T>, MutableSharedFlow<T> {
    public override var value: T
    
    public fun compareAndSet(expect: T, update: T): Boolean
}

StateFlow是一种单数据更新的热流,通过emit方法更新StateFlow的数据,通过value属性可以获取当前的数据

上面同样是利用同名工厂函数的进行创建,只是相比SharedFlow,StateFlow必须设置默认初始值

scss 复制代码
private fun stateFlow(){
    runBlocking {
        val _stateFlow = MutableStateFlow(3)
        val stateFlow = _stateFlow.asStateFlow()

        //没有发送数据时立即订阅
        stateFlow.onEach {
            Log.d(TAG, "onEach:$it")
        }.launchIn(lifecycleScope)

        //模拟发送数据
        lifecycleScope.launch(Dispatchers.IO) {
            for(i in 3..50){
                Log.d(TAG, "emit:$i")
                _stateFlow.emit(i)
                delay(50)
            }
        }

        //延迟5000毫秒去订阅,其实这时候流已经发送完了.相当于新的订阅者
        delay(5000)
        //新的订阅者
        stateFlow.onEach {
            Log.d(TAG, "new onEach:$it")
        }.launchIn(lifecycleScope)
    }
}
//输出结果:
//onEach:3
//emit:3
//emit:4
//onEach:4
//emit:5
//onEach:5
//...
//新的订阅者直接输出
//new onEach:50

从上面的输出结果看出:

  1. 没有发送数据时订阅会先接收默认值

  2. 而新发送的数据后,由于第一个值与原有值相同,直接被过滤掉了。在StateFlow中,通过Any#equals方法来判断前后两个数据是否相等。当前后两个数据相等时,数据不会被更新,订阅者也不会处理。

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

Flow 冷流 转换成StateFlow热流

StateFlow同样也有由Flow冷流转化为热流的操作符函数stateIn。

kotlin 复制代码
private fun stateIn(){
    flowOf(0,1,2,3,4).stateIn(lifecycleScope, SharingStarted.WhileSubscribed(),0)
}

shareIn函数的区别只是必须设置默认值stateIn转化的共享数据流只缓存一个最新值。这里不多说

StateFlow的原理

1.订阅者的管理类->StateFlowSlot

上面分析SharedFlow原理分析中没有单独列出来说SharedFlowSlot是SharedFlowImpl的订阅者的管理类。这里说明一下:SharedFlowImpl类中,订阅者数组中存储的对象类型为SharedFlowSlot,而在StateFlowImpl类中,订阅者数组存储的对象类型为StateFlowSlot。

less 复制代码
@SharedImmutable
private val NONE = Symbol("NONE") // 状态标识

@SharedImmutable
private val PENDING = Symbol("PENDING") // 状态标识

private class StateFlowSlot : AbstractSharedFlowSlot<StateFlowImpl<*>>() {
    // _state中默认值为null
    private val _state = atomic<Any?>(null)
    
    ...
}

根据_state保存对象的不同,可以确定StateFlowSlot类型的对象的状态。StateFlowSlot类型的对象共有四种状态:

  • null:如果_state保存的对象为空,表示当前StateFlowSlot类型的对象没有被任何订阅者使用。
  • NONE:如果_state保存的对象为NONE标识,表示当前StateFlowSlot类型的对象已经被对应的订阅者使用,但既没有挂起,也没有在处理当前的数据。
  • PENDING:如果_state保存的对象为PENDING标识,表示当前StateFlowSlot类型的对象已经被对应的订阅者使用,并且将开始处理当前的数据。
  • CancellableContinuationImpl :如果_state保存的对象为续体,表示当前StateFlowSlot类型的对象已经被对应的订阅者使用,但是订阅者已处理完当前的数据,所在的协程已被挂起,等待新的数据到来。

在StateFlowSlot类中,重写了AbstractSharedFlowSlot类的allocateLocked方法与freeLocked方法,两个方法会对订阅者的初始状态和最终状态进行改变,代码如下:

kotlin 复制代码
// 新订阅者申请使用当前的StateFlowSlot类型的对象
override fun allocateLocked(flow: StateFlowImpl<*>): Boolean {
    // 如果_state保存的对象不为空,
    // 说明当前StateFlowSlot类型的对象已经被其他订阅者使用
    // 返回false
    if (_state.value != null) return false
    // 走到这里,说明没有被其他订阅者使用,分配成功
    // 修改状态值为NONE
    _state.value = NONE
    // 返回true
    return true
}

// 订阅者释放已经使用的StateFlowSlot类型的对象
override fun freeLocked(flow: StateFlowImpl<*>): Array<Continuation<Unit>?> {
    // 修改状态值为null
    _state.value = null
    // 返回空数组
    return EMPTY_RESUMES
}


@JvmField
@SharedImmutable
internal val EMPTY_RESUMES = arrayOfNulls<Continuation<Unit>?>(0)

为了实现上述对订阅者状态的管理,在StateFlowSlot类中,还额外提供了三个方法用于实现对订阅者的状态的切换,代码如下:

kotlin 复制代码
// 当有状态更新成功时,会调用makePending方法,通知订阅者可以开始处理新数据
@Suppress("UNCHECKED_CAST")
fun makePending() {
    // 根据当前状态判断
    _state.loop { state ->
        when {
            // 如果未被订阅者使用,则直接返回
            state == null -> return
            // 如果已经处于PENDING状态,则直接返回
            state === PENDING -> return
            // 如果当前状态为NONE
            state === NONE -> {
                // 通过CAS的方式,将状态修改为PENDPENDING,并返回
                if (_state.compareAndSet(state, PENDING)) return
            }
            // 如果为挂起状态
            else -> {
                // 通过CAS的方法,将状态修改为NONE
                if (_state.compareAndSet(state, NONE)) {
                    // 如果修改成功,则恢复对应续体的执行,并返回
                    (state as CancellableContinuationImpl<Unit>).resume(Unit)
                    return
                }
            }
        }
    }
}

// 当订阅者每次处理完新数据(不一定处理成功)后,会调用takePending方法,表示完成处理
// 获取当前的状态,并修改新状态为NONE
fun takePending(): Boolean = _state.getAndSet(NONE)!!.let { state ->
    assert { state !is CancellableContinuationImpl<*> }
    // 如果之前的状态为PENDING,则返回true
    return state === PENDING
}

// 当订阅者没有新数据需要处理时,会调用awaitPending方法挂起
@Suppress("UNCHECKED_CAST")
// 直接挂起,获取续体
suspend fun awaitPending(): Unit = suspendCancellableCoroutine sc@ { cont ->
    assert { _state.value !is CancellableContinuationImpl<*> }
    // 通过CAS的方式,将当前的状态修改为挂起,并返回
    if (_state.compareAndSet(NONE, cont)) return@sc
    // 走到这里代表状态修改失败,说明又发射了新数据,当前的状态被修改为PENDING
    assert { _state.value === PENDING }
    // 唤起订阅者续体的执行
    cont.resume(Unit)
}
发送数据

在StateFlowImpl类中,当需要发射数据时,可以调用emit方法、tryEmit方法、compareAndSet方法,代码如下:

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

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

override fun compareAndSet(expect: T, update: T): Boolean =
    updateState(expect ?: NULL, update ?: NULL)

@Suppress("UNCHECKED_CAST")
public override var value: T
    // 拆箱
    get() = NULL.unbox(_state.value)
    // 更新数据
    set(value) { updateState(null, value ?: NULL) }
    
// 拆箱操作
@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE")
inline fun <T> unbox(value: Any?): T = if (value === this) null as T else value as T

可以发现,无论是通过emit方法、tryEmit方法还是compareAndSet方法,最终都是通过updateState方法实现数据的更新,代码如下:

kotlin 复制代码
// sequence是一个全局变量,当新的数据更新时,sequence会发生变化
// 当sequence为奇数时,表示当前数据正在更新
private var sequence = 0

// CAS方式更新当前数据的值
private fun updateState(expectedState: Any?, newState: Any): Boolean {
    var curSequence = 0
    // 获取所有的订阅者
    var curSlots: Array<StateFlowSlot?>? = this.slots
    // 加锁
    synchronized(this) {
        // 获取当前数据的值
        val oldState = _state.value
        // 如果期待数据不为空,同时当前数据不等于期待数据,则返回false
        if (expectedState != null && oldState != expectedState) return false
        // 如果新数据与老数据相同,即前后数据没有发生变化,则直接返回true
        if (oldState == newState) return true
        
        // 更新当前数据
        _state.value = newState
        // 获取全局变量
        curSequence = sequence
        // 如果为偶数,说明updateState方法没有被其他协程调用,没有并发
        if (curSequence and 1 == 0) {
            // 自增加1,表示当前正在更新数据
            curSequence++
            // 将新值保存到全局变量中
            sequence = curSequence
        } else { // 如果为奇数,说明updateState方法正在被其他协程调用,处于并发中
            // 加2后不改变奇偶性,只是表示当前数据发生了变化
            sequence = curSequence + 2
            // 返回true
            return true
        }
        // 获取当前所有的订阅者
        curSlots = slots
    }
    
    // 走到这里,说明上面不是并发调用updateState方法的情况
    
    // 循环,通知订阅者
    while (true) {
        // 遍历,修改订阅者的状态,通知订阅者
        curSlots?.forEach {
            it?.makePending()
        }
        // 加锁,判断在通知订阅者的过程中,数据是否又被更新了
        synchronized(this) {
            // 如果数据没有被更新
            if (sequence == curSequence) {
                // 加1,让sequence变成偶数,表示更新完毕
                sequence = curSequence + 1
                // 返回true
                return true
            }
            // 如果数据有被更新,则获取sequence和订阅者
            // 再次循环
            curSequence = sequence
            curSlots = slots
        }
    }
}
接受数据

调用StateFlow类型对象的collect方法,会触发订阅过程,接收emit方法发送的数据,这部分在 StateFlowImpl中实现,代码如下:

kotlin 复制代码
override suspend fun collect(collector: FlowCollector<T>) {
    // 为当前的订阅者分配一个StateFlowSlot类型的对象
    val slot = allocateSlot()
    try {
        // 如果collector类型为SubscribedFlowCollector,
        // 说明订阅者监听了订阅过程的启动,则先回调
        if (collector is SubscribedFlowCollector) collector.onSubscription()
        // 获取订阅者所在的协程
        val collectorJob = currentCoroutineContext()[Job]
        // 局部变量,保存上一次发射的数据,初始值为null
        var oldState: Any? = null
        // 死循环
        while (true) {
            // 获取当前的数据
            val newState = _state.value
            // 判断订阅者所在协程是否是存活的,如果不是则抛出异常
            collectorJob?.ensureActive()
            // 如果订阅者是第一次处理数据或者当前数据与上一次数据不同
            if (oldState == null || oldState != newState) {
                // 将数据发送给下游
                collector.emit(NULL.unbox(newState))
                // 保存当前发射数据到局部变量
                oldState = newState
            }
            
            // 修改状态,如果之前不是PENGDING状态
            if (!slot.takePending()) {
                // 则挂起等待新数据更新
                slot.awaitPending()
            }
        }
    } finally {
        // 释放已分配的StateFlowSlot类型的对象
        freeSlot(slot)
    }
}
新订阅者怎么获取获取缓存数据呢

当新订阅者出现时,StateFlow会将当前最新的数据发送给订阅者。可以通过调用StateFlowImpl类重写的常量replayCache获取当前最新的数据,代码如下:

csharp 复制代码
override val replayCache: List<T>
    get() = listOf(value)

看到这里就能明白为什么新订阅者能拿到数据。因为每次都是获取最新的value..而且还不能清楚缓存。如果清楚就会报错

kotlin 复制代码
override fun resetReplayCache() {
    throw UnsupportedOperationException("MutableStateFlow.resetReplayCache is not supported")
}

到此我们常用的sharedFlow和stateFlow就分析完了。。。。

当然我这里分析sharedFlow和stateFlow里面的原理还是没有分析很仔细。只是根据注释和流程对它们的有所了解。后续如果我们开发起来遇到问题就能很快去翻看源码找到答案。。用起来也能很随心应手~~~

相关推荐
千里马学框架4 小时前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台4 小时前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone4 小时前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc5 小时前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo6 小时前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077007 小时前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼7 小时前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone8 小时前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen8 小时前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone8 小时前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui