Kotlin之flow执行顺序分析(一看就会系列)

前言
PS:以下flow都是基于kotlin

flow对于初学者来说大部分都是处于只会用的阶段。但是flow到底是如何通过emit发送消息给collect接收的呢?估计还是云里雾里的(好吧,那个初学者就是我!)
大部分博客介绍flow都是对比RxJava来讲解。但是对于没使用过RxJava的人来说看着同样是迷迷糊糊。(我承认,这说的也是我)

先看一小段测试用例

测试代码.png

输出日志.png

通过测试用例可以明显的看出是先执行了flow代码块再执行的collect代码块。

疑问:
  • 那么它是什么时候执行flow的代码块呢?
  • 又是什么时候执行collect的代码块呢?
  • emit方法调用又是如何执行到collect里的呢?
  • 它们之前是如何交互的?
  • 又存在什么关系呢?

在我们平常开发中,两个类交互大多数情况下都是使用接口的方式。在flow中使用的同样也是接口的方式。而flowcollect这两个方法交互方式正是由interface Flowinterface FlowCollector这两个接口来衔接的

交互桥梁
FlowCollector.png
Flow.png

既然我们已经知道了它们通信的工具是接口,那我们具体来看看它们内部是如何运作的吧。

flow()源码分析

flow.png

首先我们可以看到flow()里面需要传入了一个FlowCollector的扩展方法。同时会返回一个Flow接口类型实例,而调用flow的时候实际执行的是创建一个SafeFlow类。SafeFlow类也很简单,它就继承了AbstractFlow类,并且实现了collectSafely抽象方法。从目前来看,当触发collectSafely方法时,就会回调到我们外部flow中的方法体里。也就是我们传进来的FlowCollector的扩展方法。

  • 到这一步不知道大伙会不会有新的疑惑?比如说“为啥需要传FlowCollector的扩展方法,直接传一个普通方法不就可以实现方法的回调吗?”说到这里,不得不夸夸kotlin了,方法参数支持函数(方法)传值,属实好用,它方法里面可以传递方法,可以帮我们省去不少接口的创建。那这里为什么不直接传普通方法呢?

其实这个问题很好解释,考虑到需要与collect方法通信,如果只传普通的方法,那只能做到单向通信了。collect直能发送信息,无法接收信息。而传接口类型的扩展方法就可以做到双向通信了。

我们接着看看什么时候会去触发collectSafely这个方法。我们去SafeFlow类的父类看看。

AbstractFlow.png

AbstractFlow类也是很简单的。我们可以看到在collect方法执行的时候就会触发我们上面说的collectSafely方法,说了这么多,那最终是谁调用了collect呢?这里就应用到了第一个接口Flow,因为collect方法是Flow接口内的方法,flow方法又会返回一个Flow的实例,所以flow返回的实例调用collect这个方法时就会执行flow中的代码块。

接着我们在看看collect方法里面做了什么。

collect()源码分析

collect.png

可以看到,这个方法需要Flow类型才可以调用。正好我们flow方法会返回一个Flow类型实例。而调用collect方法实际就是调用了调用者的collect方法,并且传入了一个collector: FlowCollector接口的匿名对象实例,这个匿名对象还重写了它的emit方法。如果说将此匿名对象传给类flow方法中,就可以很好的理解了为什么在flow的方法体中调用emit可以回调到collect方法中了。

既然说了如果,就是肯定没有直接传过去咯。相信细心的小伙伴其实早就发现了吧。在collect方法中还创建了一个SafeCollector类,并且把它传入到collectSafely方法中了。(PS:单从它的名字可以看出,它是一个安全的数据接收者,内部如何做的安全处理我下面没细说,如果需要了解的小伙伴可以查看我最后的参考文献)并没有直接将我们的匿名对象传递过去。那我们就看看SafeCollector类里面又做了什么吧。

SafeCollector.png

SafeCollector类其实也是继承了FlowCollector接口的,所以传递给flow方面里面可以调用emit方法,也就执行了SafeCollector类中的emit方法。
emit.png

而它要想回调到collect方法中去,它必然需要通过我们传进来的匿名对象去实现,所以我们只需要关注emitFun(collector as FlowCollector, value, this as Continuation)这个方法,因为flow中调用emit方法最终也是会执行到这一步的。单纯的看private val emitFun = FlowCollector::emit as Function3, Any?, Continuation, Any?>这段代码就是把FlowCollector::emit转换成三参数的方法调用,可能不是太好理解。我们把它编译成java的代码来看就比较清晰了。

SafeCollector类java代码1.png

可以看到,当调用emit时,它会先调用他双参的方法,在由双参方法里面构建了一个三参数的方法,并invoke了自己三参的方法,在三参方法的第一个参数,将我们的匿名对象传了进去。

SafeCollector类java代码2.png

在使用我们传入的匿名对象调用了两参的emit方法。那又有同学会问了。我们kotlin是单参的emit方法啊,双参数是怎么掉到单参的呢?

FlowCollector类java代码.png

其实我们kotlinemit方法编译成java代码就是双参的。这下应该就都清楚了吧。

总结

1、flow{}中的代码块必须由collect调用后才会执行
2、collect中的代码块由flow{}emit方法调用回调执行

参考文献:Kotlin中flow发射与接收分析
参考文献:协程三部曲之③:Flow 的使用!

上一篇:Retrofit2 的baseUrl 到底要不要以“/”结尾

下一篇:Kotlin之flow执行顺序分析(一看就会系列)

你可能感兴趣的:(Kotlin之flow执行顺序分析(一看就会系列))