什么是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() 方法的时候就能看出来
第一行最右边就能看到 这个方法块,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 == 0
,tryEmitNoCollectorsLocked
尝试发射没有锁定的收集器,如果只有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的扩展方法,比如 map
、onStart
、onCompletion
等。
当使用 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
参考文档
不做跟风党,LiveData,StateFlow,SharedFlow 使用场景对比
关于
如果感觉本文内容对你有帮助的话,麻烦点个赞。
如果内容有错误,我及时更改。