Coroutines:的Flow

·  阅读 845
Coroutines:的Flow

什么是Flow

kotlin 的Coroutines(协程)用来处理异步任务,Flow是处理异步的数据流

很简洁的一句话,我总觉得差点意思。打开kotlin官网 看代码

fun simple(): Flow<Int> = flow { // flow builder
    for (i in 1..3) {
        delay(100) 
        emit(i)
    }
}
fun main() = runBlocking<Unit> {
    //这里有一段代码 ,循环判断主线程是否被阻塞 不重要
    simple().collect { value -> println(value) } 
}
复制代码

main方法,runBlocking阻塞了代码,开启一个协程,调用了simple() 方法,点出了collect

那么Flow到底是什么呢? 先说句废话:Flow是一个接口

public interface Flow<out T> {

    @InternalCoroutinesApi
    public suspend fun collect(collector: FlowCollector<T>)
}
复制代码

回顾一下 out 。out表示泛型 协变,只能将泛型类型作为输出返回。按照上面的 simple() 方法说,就是 方法返回类型是 Flow<Int> 是Int, 那么 只能返回Int

流是冷的

代码在收集前不会运行。可以尝试把 collect... 这段去掉 像这样

fun simple(): Flow<Int> = flow { 
    println("start")
    for (i in 1..3) {
        delay(100) 
        emit(i)
    }
}
fun main() = runBlocking<Unit> {
    simple()
}
复制代码

不会打印start。代码在 collect 收集前 不会运行,有点懒加载的意思。

原理实现

进入flow方法

public fun <T> flow(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对象,然后返回类型是一个Flow接口。实例对象是 SafeFlow 并且把block当作参数传了进去。SafeFlow是一个类。

所以刚才的 simple() 方法 只是得到了一个SafeFlow的类,并没有其他操作。

看一下 simple().collect 的 collect

public suspend inline fun <T> Flow<T>.collect(crossinline action: suspend (value: T) -> Unit): Unit =
    collect(object : FlowCollector<T> {
        override suspend fun emit(value: T) = action(value)
    })
复制代码

action 是一个suspend方法,也就是 println(value)

2-4行,object了一个新的对象 重写了emit方法,然后,调用了对象的collect方法, 也就是SafeFlow方法。

public final override suspend fun collect(collector: FlowCollector<T>) {
    val safeCollector = SafeCollector(collector, coroutineContext)
    try {
        collectSafely(safeCollector)
    } finally {
        safeCollector.releaseIntercepted()
    }
}
复制代码

第一行的collector 是刚object的新对象

在第二行被包了一下,成了SafeCollector,SafeCollector也是继承了FlowCollector的类

第四行调用了collectSafely(..)方法

private class SafeFlow<T>(private val block: suspend FlowCollector<T>.() -> Unit) : AbstractFlow<T>() {
    override suspend fun collectSafely(collector: FlowCollector<T>) {
        collector.block()
    }
}
复制代码

collector.block() 这是个什么写法?看一下入参就明白了。入参是一个block() 是一个属于FlowCollector<T>.() 的方法,所以可以直接调用block。其实在写simple() 方法的时候就能看出来

EX3AzBoClZMcMvntzUWYEILqTV2FC64vOv-_tlSwTzA.png

第一行最右边就能看到 这个方法块,this是FlowCollector,实例对象是SafeCollector,所以这个emit是SafeCollector的emit。

 override suspend fun emit(value: T) {
     return suspendCoroutineUninterceptedOrReturn sc@{ uCont ->  //2
         try {
             emit(uCont, value)   //4
         } catch (e: Throwable) {
             lastEmissionContext = DownstreamExceptionElement(e)
             throw e
         }
     }
 }
 private fun emit(uCont: Continuation<Unit>, value: T): Any? {
     val currentContext = uCont.context
     currentContext.ensureActive()
     val previousContext = lastEmissionContext
     if (previousContext !== currentContext) {
         checkContext(currentContext, previousContext, value)
     }
     completion = uCont
     return emitFun(collector as FlowCollector<Any?>, value, this as Continuation<Unit>)
 }
复制代码

第二行看名字就知道是丢到协程里去执行了

第四行的emit 直接调用了 私有emit方法,value传了下去,重要在最后一行 emitFun ,第一个参数是构造函数传进来的,也就是 最开始的 simple().collect{} 括号的内容。都丢到这个 emitFun 方法中。

private val emitFun =
    FlowCollector<Any?>::emit as Function3<FlowCollector<Any?>, Any?, Continuation<Unit>, Any?>
复制代码

两个冒号是Kotlin 的 反射,打开他的字节码就能看到 熟悉的 invoke

小总结:

Flow是冷流,因为只有当collect执行的时候,才会去执行emit方法块

具体实现是用了反射,把emit的值 丢到 collect方法中

Flow操作符

transform 变换,可以把int,变换成其他的类型比如String ,再发射出去

(1..4).asFlow().transform {
    emit("this ${it}")
}.collect {
    println(it)
}
复制代码

map

把一个事件,通过函数变成另一个事件。这里把int 通过flow函数变成String

(1..5).asFlow().map{
    flow {
        emit("$it: Second")
    }
}.collect {
    it.collect { println(it) }
}
复制代码

flowOn,flow执行在IO线程

(1..4).asFlow()
    .flowOn(Dispatchers.IO)
    .collect {
        println("take ${it}")
    }
复制代码

combine 合并 ,会把两个emit ,最新的 进行合并

zip 压缩,会吧两个flow的内容,一一对应 发射

cache接住, 接住错误,再发射

onStart开始 ........

onCompletion结束.....

其实还有很多操作符就.不举例了。

SharedFlow 共享流

本身也是一个接口,Flow他们都是接口。继承关系 StateFlow : SharedFlow : Flow

在代码在collect前会收集。也就是说在collect之前,发送的emit是会按照配置和策略 被收集的。

val mSharedFlow = MutableSharedFlow<String>(
    0,     // 当新的collect收集时,需要重新传递给这个新的collect 的数量
    0,//表示 加上 replay,这个Flow还可以缓存多少个数据    缓冲容量
    onBufferOverflow  = BufferOverflow.SUSPEND      //溢出策略
)
复制代码

第一个参数:表示 当有新的订阅时,重新传递给这个新观察者的数量

第二个参数:表示 加上第一个参数后这个Flow还可以缓存多少个数据 缓冲容量

第三个参数:表示 溢出策略。BufferOverflow 有3个常量,SUSPEND 挂起,DROP_OLDEST丢弃老数据,DROP_LATEST 丢弃新数据

怎么用

fun main():Unit = runBlocking{
    //定义一个 SharedFlow对象
    val mSharedFlow = MutableSharedFlow<String>(
        4,     //重新传递数量
        10,    //缓冲容量
        onBufferOverflow  = BufferOverflow.SUSPEND      //溢出策略
    )
    launch {
        mSharedFlow.collect {
            println(it)
        }
    }
    launch {
        repeat(10){
            mSharedFlow.emit("abc:${it}")
        }
    }
}

复制代码

定义对象,使用了两个launch开启两个协程,一个进行 collect,一个进行重复发射。

会发现,程序一直在运行,无法结束。

原理实现

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

bufferCapacity0 = 重传数量 + 缓冲容量,有点奇怪 先往下看 ,实现对象 SharedFlowImpl

private class SharedFlowImpl<T>(
    private val replay: Int,
    private val bufferCapacity: Int,
    private val onBufferOverflow: BufferOverflow
) : AbstractSharedFlow<SharedFlowSlot>(), MutableSharedFlow<T>, CancellableFlow<T>, FusibleFlow<T> {
    /*
        Logical structure of the buffer

                  buffered values
             /-----------------------\
                          replayCache      queued emitters
                          /----------\/----------------------\
         +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
         |   | 1 | 2 | 3 | 4 | 5 | 6 | E | E | E | E | E | E |   |   |   |
         +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

          head == minOf(minCollectorIndex, replayIndex) // by definition
          totalSize == bufferSize + queueSize // by definition
          //...                 
    */
         private var buffer: Array<Any?>? = null
}
复制代码

kotlin 大佬已经 把数据结构 已经画好了,有两个定义,头总是 == 最小的 index 或 最小的重传index。整个长度 == 缓冲容量 + 队列容量。还有一个buffer 对象 ,Array是一个数组,确切来说是对java的数组。

emit

override suspend fun emit(value: T) {
    if (tryEmit(value)) return
    emitSuspend(value)
}

override fun tryEmit(value: T): Boolean {
    var resumes: Array<Continuation<Unit>?> = EMPTY_RESUMES
    val emitted = synchronized(this) {     //8
        if (tryEmitLocked(value)) {
            resumes = findSlotsToResumeLocked(resumes)
            true
        } else {
            false
        }
    }
    for (cont in resumes) cont?.resume(Unit)
    return emitted
}

private fun tryEmitLocked(value: T): Boolean {
    if (nCollectors == 0) return tryEmitNoCollectorsLocked(value)    //21
    if (bufferSize >= bufferCapacity && minCollectorIndex <= replayIndex) {
        when (onBufferOverflow) {
            BufferOverflow.SUSPEND -> return false  
            BufferOverflow.DROP_LATEST -> return true  
            BufferOverflow.DROP_OLDEST -> {}  
        }
    }
    enqueueLocked(value)
    bufferSize++ 
    if (bufferSize > bufferCapacity) dropOldestLocked()
    if (replaySize > replay) { 
        updateBufferLocked(replayIndex + 1, minCollectorIndex, bufferEndIndex, queueEndIndex)
    }
    return true
}
复制代码

emit一路看下来的 关键点

第8行的 synchronized

第21行,如果 nCollectors == 0tryEmitNoCollectorsLocked 尝试发射没有锁定的收集器,如果只有1个收集器 ,可以直接尝试发射,如果有大于1个的收集器,判断缓存,判断缓存溢出策略

按照最开始的代码 ,只有一个收集器,执行 tryEmitNoCollectorsLocked 方法,如果多个就执行缓存策略

private fun tryEmitNoCollectorsLocked(value: T): Boolean {
    if (replay == 0) return true
    enqueueLocked(value) 
    bufferSize++ 
    if (bufferSize > replay) dropOldestLocked()
    minCollectorIndex = head + bufferSize 
    return true
}

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
    }
    buffer.setBufferAt(head + curSize, item)
}
private fun growBuffer(curBuffer: Array<Any?>?, curSize: Int, newSize: Int): Array<Any?> {
    val newBuffer = arrayOfNulls<Any?>(newSize).also { buffer = it }
    if (curBuffer == null) return newBuffer
    val head = head
    for (i in 0 until curSize) {
        newBuffer.setBufferAt(head + i, curBuffer.getBufferAt(head + i))
    }
    return newBuffer
}
复制代码

如果 replay == 0 直接返回

否则进入 enqueueLocked ,关键的 growBuffer 方法,缓冲区生长,这个方法主要是在 修改数组长度并把数据移动到新的数组里。分配的大小是2的幂次。

到这基本上已经走完一种情况的插入的流程。

回到 tryEmitLocked 方法,如果nCollectors 不为0,会执行缓存策略。BufferOverflow.SUSPEND 返回了false。回到 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
}
override suspend fun emit(value: T) {
    if (tryEmit(value)) return // fast-path
    emitSuspend(value)
}
复制代码

一路false 返回到 emitSuspend(value) ,此时 synchronized 已经解锁了。

    private suspend fun emitSuspend(value: T) = suspendCancellableCoroutine<Unit> sc@{ cont ->
        var resumes: Array<Continuation<Unit>?> = EMPTY_RESUMES
        val emitter = synchronized(this) lock@{
            if (tryEmitLocked(value)) {
                cont.resume(Unit)
                resumes = findSlotsToResumeLocked(resumes)
                return@lock null
            }
            // add suspended emitter to the buffer
            Emitter(this, head + totalSize, value, cont).also {
                enqueueLocked(it)
                queueSize++ 
                if (bufferCapacity == 0) resumes = findSlotsToResumeLocked(resumes)
            }
        }
        emitter?.let { cont.disposeOnCancellation(it) }
        for (r in resumes) r?.resume(Unit)
    }
复制代码

此时进入了一个协程。又是一个 锁 ,又进行了 tryEmitLocked 尝试发射。成功直接就返回了。如果还是false失败,初始化了一个 Emitter 对象, 协程执行,把 emmiter 带了进去。再往里面看。

internal open class CancellableContinuationImpl<in T>() .... {
    private val _state = atomic<Any?>(Active)
}
复制代码

看到 atomic ,肯定就是一个 CAS 操作。也就是说 tryEmitLocked 方法尝试CAS操作,挂起再执行。

到这里又回到了tryEmitLocked 方法,一个发射的流程基本看完。

接下来是collect

mSharedFlow.collect {    //1
    println(it)
}

public suspend inline fun <T> Flow<T>.collect(crossinline action: suspend (value: T) -> Unit): Unit =
    collect(object : FlowCollector<T> {
        override suspend fun emit(value: T) = action(value)
    })

override suspend fun collect(collector: FlowCollector<T>) {
    val slot = allocateSlot()          //11
    try {
        if (collector is SubscribedFlowCollector) collector.onSubscription()
        val collectorJob = currentCoroutineContext()[Job]
        while (true) {         //15
            var newValue: Any?
            while (true) {     //17
                newValue = tryTakeValue(slot) 
                if (newValue !== NO_VALUE) break
                awaitValue(slot) 
            }
            collectorJob?.ensureActive()
            collector.emit(newValue as T)
        }
    } finally {
        freeSlot(slot)
    }
}

private fun tryTakeValue(slot: SharedFlowSlot): Any? {
    var resumes: Array<Continuation<Unit>?> = EMPTY_RESUMES
    val value = synchronized(this) {
        val index = tryPeekLocked(slot)
        if (index < 0) {
            NO_VALUE
        } else {
            val oldIndex = slot.index
            val newValue = getPeekedValueLockedAt(index)
            slot.index = index + 1 
            resumes = updateCollectorIndexLocked(oldIndex)
            newValue
        }
    }
    for (resume in resumes) resume?.resume(Unit)
    return value
}
复制代码

第1行的collect 还是一个扩展方法,还是会被FlowCollector包裹一下,丢到第10行 具体的collect 的实现里面。

第11行,。。。贴不动了。。。,分配一个位置,里面有一个nCollectors++ 表示 收集者+1,也是为了呼应前面 有多个collect 继续执行 溢出策略,因为SharedFlow 是可以有多个 收集者 也就是 mSharedFlow.collect{}

第15行 有两个 while (true) , SharedFlow的collect 之后的代码 无法执行,这里有点奇怪,我断点打了很多,看样子是一直在这里死循环,但断点正常走完之后就没了。

tryTakeValue 尝试拿一个值,又一个 synchronized 。tryPeekLocked() 去拿一个索引,然后根据这个索引,通过 getPeekedValueLockedAt 去拿值。拿完值后 指向下一个索引。

updateCollectorIndexLocked 这个方法里面就更复杂了,不继续看了,看名字就知道 更新索引位置、缓存。比如把一个数组里的第一个值删除,总要修改很多标记位。

回到17行while true,如果新值不为 NO_VALUE 就break,否则 awaitValue ,这里面会继续查看一下索引,再去根据这个索引去取值,里面的循环 是循环取值。

第15行 外面的循环,就是循环发射emit值。会有点奇怪 collector: FlowCollector<T> 这个对象是什么,这个我也是打了断点才看到,是编译生成的类对象,执行编译生成的emit方法 ,最后到collect{...}里面。

小总结一下:

1、SharedFlow collect后 永远不会被完成

2、emit和collect 都是有 synchronized 的,生产者消费者模式

3、不防抖,如果 oldValue == newValue 还是会发射

4、可以配置replay粘性事件,为0则没有粘性事件

5、本质是一个数组结构

StateFlow状态流

怎么用

fun main(){
    runBlocking {
        val stateFlow = MutableStateFlow("aaa")
        launch {
            stateFlow.collect {
                println("launch1:${it}")
            }
        }
        launch {
            stateFlow.emit("bbb")
        }
    }
}
复制代码

定义一个MutableStateFlow ,初始化时必须有个初始值,总得有个状态。一个collect,一个emit

看上去与SharedFlow没什么太大区别,只是StateFlow只能有一个数据。

原理实现

StateFlow结构不是非常复杂,毕竟有着继承关系,有这自己的能力,也限制了其他东西。

private class StateFlowImpl<T>(
    initialState: Any  
) : AbstractSharedFlow<StateFlowSlot>(), MutableStateFlow<T>, CancellableFlow<T>, FusibleFlow<T> {
    private val _state = atomic(initialState)  

    public override var value: T
        get() = NULL.unbox(_state.value)
        set(value) { updateState(null, value ?: NULL) }

    override suspend fun emit(value: T) {    //10
        this.value = value
    }
    
    private fun updateState(expectedState: Any?, newState: Any: Boolean {
        //..
        synchronized(this) {         //16
             //..
            if (oldState == newState) return true 
            _state.value = newState
            //..
        }
        //..
    }

    override suspend fun collect(collector: FlowCollector<T>) {
        val slot = allocateSlot()
        try {
            //..
            while (true) {
                //..
                val newState = _state.value
                //..
                collectorJob?.ensureActive()         //25
                // Conflate value emissions using equality
                if (oldState == null || oldState != newState) {
                    collector.emit(NULL.unbox(newState))
                    oldState = newState
                }
                //..
            }
        } finally {  //..
        }
    }
}
复制代码

第10行 emit,后会触发 updateState 方法

第16行 又是 synchronized , 如果过 oldState == newState 旧值 等于新值 直接返回。

第25行 又是while、true,确保协程还是活动的,发射新值

小总结一下:

1、StateFlow collect后 永远不会被完成

2、存是有 synchronized 的,一个写,可以有多个读

3、防抖的,如果 oldValue == newValue 状态不会被改变

4、默认有粘性事件,当一个新的collect时候,会获取最新的值

5、本质是一个atomic泛型 ,必须要有个初始值

其他方法

SharedFlow 、StateFlow 都是继承Flow的,可以使用大部分Flow的扩展方法,比如 maponStartonCompletion 等。

当使用 flowOn() 时候会发现只有 Flow能用,这里做个猜想,Flow真正执行是通过反射执行,而其他两个是 被 放到了协程中执行,导致的。如果有大佬知道,求教。

如果想切换线程,可以直接配置协程的 Dispatchers

MutableSharedFlow、MutableStateFlow 可以 .asSharedFlow().asStateFlow() 变成只读对象,防止外部使用。

总结

SharedFlow 、StateFlow collect后 永远不会被完成

SharedFlow 不防抖,StateFlow 防抖

SharedFlow 读写都有synchronized ,StateFlow 写有synchronized

SharedFlow 可配置粘性,StateFlow 有粘性

SharedFlow 不允许emit null,StateFlow 允许 null。

Flow 感知不了生命周期,可以通过协程去解决。

至于总线的BUS

我也简单写了一个,依赖于SharedFlow,再加上kotlin的语法糖。

//发射
postEvent(ChangeUIEvent(" msg"))

//订阅
observeEvent<ChangeUIEvent>(this, Dispatchers.Main){
    Log.e("ChangeUIEvent:","${it.msg}")
}
复制代码

几乎没什么复杂的原理,bus总线就是依赖一个全局的对象,订阅这个对象就行,稍微简单的封装一下就好。
我用了广播,利用广播跨进程传递数据,传递后 继续postEvent(..) 发送到当前进程。
1月20日修改代码,改为AIDL方式传递数据,需要传递的数据继承Serializable,就可以了。

open class ChangeUIEvent(var msg:String) :Serializable

val changeUIEvent = ChangeUIEvent("SecondActivity msg")
postEventProcessAIDL(changeUIEvent)
复制代码

结构: app进程,有一个Service。本身也初始化了回调,连接了Service。Service中做了一个集合收集回调。
第二个Activity 初始化了回调,连接Service。

代码放到了Github

参考文档

kotlin官网

不做跟风党,LiveData,StateFlow,SharedFlow 使用场景对比

关于

如果感觉本文内容对你有帮助的话,麻烦点个赞。

如果内容有错误,我及时更改。

分类:
Android
标签:
分类:
Android
标签:
收藏成功!
已添加到「」, 点击更改