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

相关推荐
niurenwo31 分钟前
Android深入理解包管理--记录存储模块
android
文 丰1 小时前
【Android Studio】使用雷电模拟器调试
android·ide·android studio
蓝影铁哥2 小时前
SpringBoot3核心特性-核心原理
android·java·数据库·spring boot
吾爱星辰2 小时前
Kotlin 基本介绍(一)
android·开发语言·kotlin
一杯凉白开4 小时前
Now in Android !AndroidApp开发的最佳实践,让我看看是怎么个事?
android·架构·android jetpack
落落落sss6 小时前
项目集成sharding-jdbc
android·java·数据库·spring·mybatis
哈哈皮皮虾的皮6 小时前
安卓开发中,可以反射去换肤,那么我们应该也可以使用反射去串改我们的程序,作为开发者,我们如何保证我们自己的应用的安全呢?
android·网络·安全
是阿臻6 小时前
在 macOS 上安装 ADB给安卓手机装APK,同样适用智能电视、车机
android·macos·adb
不太会写7 小时前
dhtmlxGantt 甘特图 一行展示多条任务类型
android
中式代码美式咖啡7 小时前
在Spring Boot中实现多环境配置
android·spring boot·后端