美文网首页kotlinKotlinAndroid进阶之路
Kotlin之flow执行顺序分析(一看就会系列)

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

作者: 我叫Xy | 来源:发表于2021-12-06 17:56 被阅读0次
    前言
    PS:以下flow都是基于kotlin

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

    先看一小段测试用例
    测试代码.png
    输出日志.png
    通过测试用例可以明显的看出是先执行了flow代码块再执行的collect代码块。
    疑问:
    • 那么它是什么时候执行flow的代码块呢?
    • 又是什么时候执行collect的代码块呢?
    • emit方法调用又是如何执行到collect里的呢?
    • 它们之前是如何交互的?
    • 又存在什么关系呢?

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

    交互桥梁
    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<T>接口的匿名对象实例,这个匿名对象还重写了它的emit方法。如果说将此匿名对象传给类flow方法中,就可以很好的理解了为什么在flow的方法体中调用emit可以回调到collect方法中了。

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

    SafeCollector.png
    SafeCollector类其实也是继承了FlowCollector<T>接口的,所以传递给flow方面里面可以调用emit方法,也就执行了SafeCollector类中的emit方法。
    emit.png
    而它要想回调到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的代码来看就比较清晰了。 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执行顺序分析(一看就会系列)

        本文链接:https://www.haomeiwen.com/subject/ngmdxrtx.html