版权声明:本文由晋中望原创文章,转载请注明出处: 
文章原文链接:https://www.qcloud.com/community/article/123

来源:腾云阁 https://www.qcloud.com/community

相信很多做Android或是Java研发的同学对RxJava应该都早有耳闻了,尤其是在Android开发的圈子里,RxJava渐渐开始广为流行。同样有很多同学已经开始在自己的项目中使用RxJava。它能够帮助我们在处理异步事件时能够省去那些复杂而繁琐的代码,尤其是当某些场景逻辑中回调中嵌入回调时,使用RxJava依旧能够让我们的代码保持极高的可读性与简洁性。不仅如此,这种基于异步数据流概念的编程模式事实上同样也能广泛运用在移动端这种包括网络调用、用户触摸输入和系统弹框等在内的多种响应驱动的场景。那么现在,就让我们一起分析一下RxJava的响应流程吧。
(本文基于RxJava-1.1.3)

一.用法

首先来看一个简单的例子:

运行结果为:

从结果中我们不难看出整体的调用流程:

首先通过调用Observable.create()方法生成一个被观察者,紧接着在这里我们又调用了map()方法对原被观察者进行数据流的变换操作,生成一个新的被观察者(为何是新的被观察者后文会讲),最后调用subscribe()方法,传入我们的观察者,这里观察者订阅的则是调用map()之后生成的新被观察者。

在整个过程中我们会注意到三个主角:Observable、OnSubscribe、Subscriber,所有的操作都是围绕它们进行的。不难看出这里三个角色的分工:

  • Observable:被观察者的来源,亦或说是被观察者本身
  • OnSubscribe:用来通知观察者的不同行为
  • Subscriber:观察者,通过实现对应方法来产生具体的处理。

所以接下来我们以这三个角色为中心来分析具体的流程。

二.分析

1.订阅过程

首先我们进入Observable.create()看看:

这里调用构造函数生成了一个Observable对象并将传入的OnSubscribe赋给自己的成员变量onsubscribe,等等,这个hook是从哪里冒出来的?我们向上找:

RxJavaObservableExecutionHook这个抽象Proxy类默认对OnSubscribe对象不做任何处理,不过通过继承该类并重写onCreate()等方法我们可以对这些方法对应的时机做一些额外处理比如打Log或者一些数据收集方面的工作。

到目前最初始的被观察者已经生成了,我们再来看看观察者这边。我们知道通过调用observable.subscribe()方法传入一个观察者即构成了观察者与被观察者之间的订阅关系,那么这内部又是如何实现的呢?看代码:

这里我们略去部分无关代码看主要部分,subscribe.onStart()默认空实现我们暂且不用管它,对于传进来的subscriber要包装成SafeSubscriber,这个SafeSubscriber对原来的subscriber的一系列方法做了更完善的处理,包括:onError()onCompleted()只会有一个被执行;保证一旦onError()或者onCompleted()被执行,将不再能再执onNext()等情况。这里封装为SafeSubscriber之后,调用onSubscribe.call(),并将subscriber传入,这样就完成了一次订阅。

显而易见,Subscriber作为观察者,在订阅行为完成后,其具体行为在整个链式调用中起着至关重要的作用,我们来看看它内部的构成的主要部分:


每个Subscriber都持有一个SubscriptionList,这个list保存的是所有该观察者的订阅事件,同时Subscriber也对应实现了Subscription接口,当这个Subscriber取消订阅的时候会将持有事件列表中的所有Subscription取消订阅,并且从此不再接受任何订阅事件。同时,通过Producer可以去限定该Subscriber所接收的数据流的总量,这个限制量其实是加在Subscriber.onNext()方法上的,onComplete()onError()则不会受到其影响。因为是底层抽象类,onNext()onComplete()onError()统一不在这里处理。

2.变换过程

在收到Observable的消息之前我们有可能会对数据流进行处理,例如map()、flatMap()、deBounce()、buffer()等方法,本例中我们用了map()方法,它接收了原被观察者发射的数据并将通过该方法返回的结果作为新的数据发射出去,相当于做了一层中间转化:

我们接着看这个转化过程:

这里是通过一个lift()方法实现的,再查看其他的转化方法发现内部也都使用lift()实现的,看来这个lift()就是关键所在了,不过不急,我们先来看看这个OperationMap是什么:

OperationMap实现了Operator接口的call()方法,该方法接受外部传入的观察者,并将其作为参数构造出了一个新的观察者,我们不难发现o.onNext(transformer.call(t));这一句起了至关重要的作用,这里的接口transformer将泛型T转化为泛型R:

这样之后,再将转换后的数据传回至原观察者的onNext()方法,就完成了观察数据流的转化,但是你应该也注意到了,我们用来做转换的这个新的观察者并没有实现订阅被观察者的操作,这个订阅操作又是在哪里实现的呢?答案就是接下来的lift()

在这里我们新生成了一个Observable对象,在这个新对象的onSubscribe成员的call()方法中我们通过operator.call()拿到之前生成的未产生订阅的观察者st,之后将它作为参数传入一开始的onSubscribe.call()中,即完成了这个中间订阅的过程。
现在我们将整个流程梳理一下:

  • 一次map()变换

  • 根据Operator实例生成新的Subscriber

  • 通过lift()生成新的Observable

  • 原Subscriber订阅新的Observavble

  • 新的Observable中onSubscribe通知新Subscriber订阅原Observable

  • 新Subscriber将消息传给原Subscriber。

为了便于理解,这里借用一下扔物线的图:

以上就是一次map()变换的流程,事实上多次map()也是同样道理:最外层的目标Subscriber发生订阅行为后,onSubscribe.onNext()会逐层嵌套调用,直至初始Observable被最底层的Subscriber订阅,通过Operator的一层层变化将消息传到目标Subscriber。再次祭出扔物线的图:

至于其他的多种变化的实现流程也都很类似,借助于Operator的不同实现来达到变换数据流的目的。例如其中的flatMap(),它需要进行两次lift(),其中第二次是OperationMerge,将转换成的每一个Observable数据流通过InnerSubscriber这个纽带订阅后,在InnerSubscriber的onNext()中拿到R,再通过传入的parent(也就是原MergeSubscriber)将它们全部发射(emit)出去,由最外层我们传入的Subscriber统一接收,这样就完成了 T => Observable<R> => R 的转化:




除此之外,还有许多各式各样的操作符,如果它们还不能满足你的需要,你也可以通过实现Operator接口定制新的操作符。灵活运用它们往往能达到事半功倍的效果,比如通过使用sample()debounce()等操作符有效避免backpressure的需要等等,这里就不一一介绍了。

下篇将继续从"线程切换过程"开始分析

文章来源公众号:QQ空间终端开发团队(qzonemobiledev)

RxJava && Agera 从源码简要分析基本调用流程(1)的更多相关文章

  1. RxJava && Agera 从源码简要分析基本调用流程(2)

    版权声明:本文由晋中望原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/124 来源:腾云阁 https://www.qclo ...

  2. Activity源码简要分析总结

    Activity源码简要分析总结 摘自参考书籍,只列一下结论: 1. Activity的顶层View是DecorView,而我们在onCreate()方法中通过setContentView()设置的V ...

  3. [Java] LinkedHashMap 源码简要分析

    特点 * 各个元素不仅仅按照HashMap的结构存储,而且每个元素包含了before/after指针,通过一个头元素header,形成一个双向循环链表.使用循环链表,保存了元素插入的顺序. * 可设置 ...

  4. [Java] HashMap 源码简要分析

    特性 * 允许null作为key/value. * 不保证按照插入的顺序输出.使用hash构造的映射一般来讲是无序的. * 非线程安全. * 内部原理与Hashtable类似.   源码简要分析 pu ...

  5. [Java] Hashtable 源码简要分析

    Hashtable /HashMap / LinkedHashMap 概述 * Hashtable比较早,是线程安全的哈希映射表.内部采用Entry[]数组,每个Entry均可作为链表的头,用来解决冲 ...

  6. 源码级分析Android系统启动流程

    首先看一下Android系统的体系结构,相信大家都不陌生 1.首先Bootloader引导程序启动完Linux内核后,会加载各种驱动和数据结构,当有了驱动以后,开始启动Android系统,同时会加载用 ...

  7. Redis源码简要分析

    转载请注明来源:https://www.cnblogs.com/hookjc/ 把所有服务端文件列出来,并且标示出其作用:adlist.c //双向链表ae.c //事件驱动ae_epoll.c // ...

  8. Elasticsearch之client源码简要分析

    问题 让我们带着问题去学习,效率会更高 1  es集群只配置一个节点,client是否能够自动发现集群中的所有节点?是如何发现的? 2  es client如何做到负载均衡? 3  一个es node ...

  9. spring mvc 源码简要分析

    关于web项目,运用比较多的是过滤器和拦截器 过滤器基于责任链设计模式 创建过滤器链 / Create the filter chain for this requestApplicationFilt ...

随机推荐

  1. linux中断编程

    本文档只介绍中断编程所需的函数及应用,中断完整处理流程应参考文档<linux中断处理流程>,可参考文档<linux内核对中断的处理方式>对中断初步了解. 本文档基于3.14内核 ...

  2. epel源报错怎么解决?

    http://mirrors.aliyun.com/centos/6/extras/x86_64/repodata/81fdefdd048c01dcc6cda1fd53aacec2a0613ea10d ...

  3. file文件与base64字符串的相互转换

    今天心情不好,不想说话. /** * 文件转base64字符串 * @param file * @return */ public static String fileToBase64(File fi ...

  4. 用CSS创建打印页面

    用CSS创建打印页面,不必为打印而专门建立一个HTML文件,可以节省一些体力,其前提是按“WEB标准”用CSS+DIV布局HTML页面. 第一.在HTML页面加入为打印机设置的CSS文件 <li ...

  5. html测试代码框工具

    Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ --& ...

  6. Hibernate一级缓存、二级缓存以及查询缓存的关系

    转载自http://blog.csdn.net/maoyeqiu/article/details/50209893 前两天总结了一下二级缓存和查询缓存的关系,但是又有一个新的问题,就是查询缓存缓存到二 ...

  7. e674. 创建并绘制加速图像

    Images in accelerated memory are much faster to draw on the screen. This example demonstrates how to ...

  8. e642. 处理拖放事件

    The drop target in this example only accepts dropped String objects. A drop target must implement Dr ...

  9. 使用ffmpeg的av_read_frame,如何控制连接超时

    最近使用ffmpeg来做一个rtsp的客户端,这过程也遇到不少问题,不过相应都比较好,一路走下来.不过到项目结尾时,且遇到一个比较纠结的问题.那就是客户端在使用的过程中,把rtsp服务器的网断了.这时 ...

  10. [ACM] POJ 3349 Snowflake Snow Snowflakes(哈希查找,链式解决冲突)

    Snowflake Snow Snowflakes Time Limit: 4000MS   Memory Limit: 65536K Total Submissions: 30512   Accep ...