前言
PS:以下flow
都是基于kotlin
flow
对于初学者来说大部分都是处于只会用的阶段。但是flow
到底是如何通过emit
发送消息给collect
接收的呢?估计还是云里雾里的(好吧,那个初学者就是我!)
大部分博客介绍flow
都是对比RxJava
来讲解。但是对于没使用过RxJava
的人来说看着同样是迷迷糊糊。(我承认,这说的也是我😂)
先看一小段测试用例
通过测试用例可以明显的看出是先执行了
flow
代码块再执行的collect
代码块。
疑问:
- 那么它是什么时候执行
flow
的代码块呢? - 又是什么时候执行
collect
的代码块呢? -
emit
方法调用又是如何执行到collect
里的呢? - 它们之前是如何交互的?
- 又存在什么关系呢?
在我们平常开发中,两个类交互大多数情况下都是使用接口的方式。在flow
中使用的同样也是接口的方式。而flow
和collect
这两个方法交互方式正是由interface Flow<out T>
和interface FlowCollector<in T>
这两个接口来衔接的
交互桥梁
既然我们已经知道了它们通信的工具是接口,那我们具体来看看它们内部是如何运作的吧。
flow()源码分析
首先我们可以看到
flow()
里面需要传入了一个FlowCollector
的扩展方法。同时会返回一个Flow
接口类型实例,而调用flow
的时候实际执行的是创建一个SafeFlow
类。SafeFlow
类也很简单,它就继承了AbstractFlow
类,并且实现了collectSafely
抽象方法。从目前来看,当触发collectSafely
方法时,就会回调到我们外部flow
中的方法体里。也就是我们传进来的FlowCollector
的扩展方法。
- 到这一步不知道大伙会不会有新的疑惑?比如说“为啥需要传
FlowCollector
的扩展方法,直接传一个普通方法不就可以实现方法的回调吗?”说到这里,不得不夸夸kotlin
了,方法参数支持函数(方法)传值,属实好用,它方法里面可以传递方法,可以帮我们省去不少接口的创建。那这里为什么不直接传普通方法呢?
其实这个问题很好解释,考虑到需要与collect
方法通信,如果只传普通的方法,那只能做到单向通信了。collect
直能发送信息,无法接收信息。而传接口类型的扩展方法就可以做到双向通信了。
我们接着看看什么时候会去触发collectSafely
这个方法。我们去SafeFlow
类的父类看看。
AbstractFlow
类也是很简单的。我们可以看到在collect
方法执行的时候就会触发我们上面说的collectSafely
方法,说了这么多,那最终是谁调用了collect
呢?这里就应用到了第一个接口Flow
,因为collect
方法是Flow
接口内的方法,flow
方法又会返回一个Flow
的实例,所以flow
返回的实例调用collect
这个方法时就会执行flow
中的代码块。
接着我们在看看collect
方法里面做了什么。
collect()源码分析
可以看到,这个方法需要
Flow
类型才可以调用。正好我们flow
方法会返回一个Flow
类型实例。而调用collect
方法实际就是调用了调用者的collect
方法,并且传入了一个collector: FlowCollector<T>
接口的匿名对象实例,这个匿名对象还重写了它的emit
方法。如果说将此匿名对象传给类flow
方法中,就可以很好的理解了为什么在flow
的方法体中调用emit
可以回调到collect
方法中了。
既然说了如果,就是肯定没有直接传过去咯。相信细心的小伙伴其实早就发现了吧。在collect
方法中还创建了一个SafeCollector
类,并且把它传入到collectSafely
方法中了。(PS:单从它的名字可以看出,它是一个安全的数据接收者,内部如何做的安全处理我下面没细说,如果需要了解的小伙伴可以查看我最后的参考文献)并没有直接将我们的匿名对象传递过去。那我们就看看SafeCollector
类里面又做了什么吧。
SafeCollector
类其实也是继承了FlowCollector<T>接口的,所以传递给flow
方面里面可以调用emit
方法,也就执行了SafeCollector
类中的emit
方法。而它要想回调到
collect
方法中去,它必然需要通过我们传进来的匿名对象去实现,所以我们只需要关注emitFun(collector as FlowCollector<Any?>, value, this as Continuation<Unit>)
这个方法,因为flow
中调用emit
方法最终也是会执行到这一步的。单纯的看private val emitFun = FlowCollector<Any?>::emit as Function3<FlowCollector<Any?>, Any?, Continuation<Unit>, Any?>
这段代码就是把FlowCollector<Any?>::emit
转换成三参数的方法调用,可能不是太好理解。我们把它编译成java的代码来看就比较清晰了。
可以看到,当调用
emit
时,它会先调用他双参的方法,在由双参方法里面构建了一个三参数的方法,并invoke
了自己三参的方法,在三参方法的第一个参数,将我们的匿名对象传了进去。
在使用我们传入的匿名对象调用了两参的
emit
方法。那又有同学会问了。我们kotlin
是单参的emit
方法啊,双参数是怎么掉到单参的呢?
其实我们
kotlin
中emit
方法编译成java代码就是双参的。这下应该就都清楚了吧。
总结
1、flow{}
中的代码块必须由collect
调用后才会执行
2、collect
中的代码块由flow{}
中emit
方法调用回调执行
参考文献:Kotlin中flow发射与接收分析
参考文献:协程三部曲之③:Flow 的使用!