RxJava && Agera 从源码简要分析基本调用流程(1)
版权声明:本文由晋中望原创文章,转载请注明出处:
文章原文链接: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)的更多相关文章
- RxJava && Agera 从源码简要分析基本调用流程(2)
版权声明:本文由晋中望原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/124 来源:腾云阁 https://www.qclo ...
- Activity源码简要分析总结
Activity源码简要分析总结 摘自参考书籍,只列一下结论: 1. Activity的顶层View是DecorView,而我们在onCreate()方法中通过setContentView()设置的V ...
- [Java] LinkedHashMap 源码简要分析
特点 * 各个元素不仅仅按照HashMap的结构存储,而且每个元素包含了before/after指针,通过一个头元素header,形成一个双向循环链表.使用循环链表,保存了元素插入的顺序. * 可设置 ...
- [Java] HashMap 源码简要分析
特性 * 允许null作为key/value. * 不保证按照插入的顺序输出.使用hash构造的映射一般来讲是无序的. * 非线程安全. * 内部原理与Hashtable类似. 源码简要分析 pu ...
- [Java] Hashtable 源码简要分析
Hashtable /HashMap / LinkedHashMap 概述 * Hashtable比较早,是线程安全的哈希映射表.内部采用Entry[]数组,每个Entry均可作为链表的头,用来解决冲 ...
- 源码级分析Android系统启动流程
首先看一下Android系统的体系结构,相信大家都不陌生 1.首先Bootloader引导程序启动完Linux内核后,会加载各种驱动和数据结构,当有了驱动以后,开始启动Android系统,同时会加载用 ...
- Redis源码简要分析
转载请注明来源:https://www.cnblogs.com/hookjc/ 把所有服务端文件列出来,并且标示出其作用:adlist.c //双向链表ae.c //事件驱动ae_epoll.c // ...
- Elasticsearch之client源码简要分析
问题 让我们带着问题去学习,效率会更高 1 es集群只配置一个节点,client是否能够自动发现集群中的所有节点?是如何发现的? 2 es client如何做到负载均衡? 3 一个es node ...
- spring mvc 源码简要分析
关于web项目,运用比较多的是过滤器和拦截器 过滤器基于责任链设计模式 创建过滤器链 / Create the filter chain for this requestApplicationFilt ...
随机推荐
- win7 64位安装oracle10g客户端心得
用了整整两天时间才在64位Win7下装好了Oracle的开发环境(包括Oracle的客户端和第三方客户端工具),过程原来和32位类似,注意不能下载64位的安装包. 安装过程: 1.下载Oracle 1 ...
- CentOS6.5下安装Nexus
一.环境准备 (1) CentOS6.5 (2) nexus-latest-bundle.tar.gz 下载地址:http://www.sonatype.org/nexus/archived/ 二.安 ...
- [LintCode]判断一个字符串是否包含另一个字符串的所有字符
问题描述: 比较两个字符串A和B,确定A中是否包含B中所有的字符.字符串A和B中的字符都是 大写字母. 样例 给出 A = "ABCD" B = "ACD",返 ...
- cat /etc/init.d/nfs 这句话看不懂
if status rpc.mountd > /dev/null ; then exit 0 fi Linux非root用户程序使用小于1024端口 在Linux下,默认端口1024下的是 ...
- DataGridView使用技巧十一:DataGridView用户输入时,单元格输入值的设定
通过DataGridView.CellParsing事件可以设定用户输入的值.下面的示例:当输入英文文本内容的时候,立即被改变为大写.DataGridView.CellParsing在离开编辑的单元格 ...
- Tomcat负载均衡和集群环境的搭建
实现此集群的方法参考了网上的很多文章,但由于很多文章都表明是原创的,故无法知道整个操作流程的真正作者是谁.下面就是我用我们真实的项目去实现这个过程.同时修复这过程中一些问题.以下的所有步骤均为亲自测试 ...
- Hadoop2的Yarn和MapReduce2相关
转自: http://www.aboutyun.com/thread-7678-1-1.html.. 问题导读: 1.什么是yarn? 2.Yarn 和MapReduce相比,它有什么特殊作用 ? ...
- Hive SQL执行流程分析
转自 http://www.tuicool.com/articles/qyUzQj 最近在研究Impala,还是先回顾下Hive的SQL执行流程吧. Hive有三种用户接口: cli (Command ...
- thinkphp3.2 常用入口文件
<?php define('DIR_SECURE_FILENAME', 'default.html'); define('APP_PATH','./index/'); //项目路径 requir ...
- Spring bean的初始化及销毁
Spring bean的几个属性:scope.init-method.destroy-method.depends-on等. Scope 在Spring容器中是指其创建的Bean对象相对于其他Bean ...