扒开RxJava的骨头【RxJava系列之RxJava的原理】

前言

在前面的文章中,我们已经介绍了,

相信大家对RxJava的基础知识,已经是了如指掌了。

接下来,就让我们对RxJava的原理,一探究竟!

本篇文章致力成为全网最通俗易懂最细致的RxJava原理讲解文章
本篇文章将会带你手写RxJava核心原理

一. 原理是什么

RxJava的原理,其实就是两个设计模式的应用。即观察者模式和装饰器模式。

二. 关于RxJava的观察者模式

普通的观察者模式

我们知道,普通的观察者模式,都是观察者订阅被观察者 。同时一个被观察者可以对应多个观察者。举个很形象的例子:电灯和开关。

按下开关,电灯就亮。在这里,就是"电灯"在观察着"开关",即观察者订阅被观察者的变化。当电灯观察到开关被按下时,就会做出亮灯的动作。同时,一个开关还可以控制多个灯泡,即一个被观察者可以同时对应多个观察者。

这是一个很常见的例子,相信不难理解。

RxJava的观察者模式

可是RxJava的观察者模式,和普遍的观察者模式有两点不同。一是被观察者订阅观察者,二是一个被观察者通常只对应一个观察者。

但,它的基本思想内核,还是观察者模式,只是形式不同而已。

比如这个"被观察者订阅观察者",听起来感觉比较奇怪,但为什么会是这样呢?原因很简单,就是为了保持代码链式调用的连续性,保持响应式编程的特点,不让它中断

我们平时总说,要让用户体验流畅,不让它卡住或者中断。对于RxJava来说,我们就是RxJava口中的用户,所以他们也非常照顾我们的"用户体验"

比如下面这个例子,

蓝圈部分是被观察者 的部分,绿圈部分是观察者 的部分,中间红框就是订阅方法。可以看到,这样写就能够保证中间不中断,事件是从被观察者"流"出来的,更能突出响应式编程的特点。

类比开关和灯泡的例子,就是说,开关订阅了灯泡,开关发生了变化,推动着灯泡亮,开关成为了主导者,即被观察者是事件的上游,推动事件流的发展 。而不是观察者在下游一直盯着,耗费人力物力,ROI很低。(这个其实就是响应式编程的特点,具体可看[这一篇文章](我们为什么要学RxJava?【RxJava系列之问个为什么】 - 掘金 (juejin.cn)))

综上,RxJava的观察者模式,是改装后的观察者模式,是更适合RxJava框架的观察者模式。

三. RxJava的装饰器模式

上篇文章也讲过了装饰器模式,讲的非常详细,反而有可能会让你听不太懂。所以在这里我又对装饰器模式进行了精简,抽出其核心思想,方便我们理解RxJava的装饰器模式。

普通的装饰器模式

举个实际的例子,

(1)首先,有一个接口,表示某样东西"可以喝"

(2)然后有一个具体实现类,比如咖啡

这个类其实用不到,放到这里只是方便理解。

到这里,就是一个很普通的例子:一个接口,同时还有一个接口实现类,无任何理解压力。接下来就到了装饰器模式的部分了

(3)一个针对咖啡的装饰器类(抽象类)

注意这里的注释,很重要,即本身要实现接口,而且还要能装下一个接口 。本身实现接口的目的之一就是能够灵活实现可插拔的效果(可看[上一篇文章](RxJava的前世【RxJava系列之设计模式】 - 掘金 (juejin.cn))关于装饰器模式的介绍),以方便自己被另外一个装饰器类装下,其实还是服务于目的一。装下一个接口的目的就是,利用装下的这个接口,实现对装下的这个接口的功能的增强,因为这个接口,就是其他的装饰器或者具体类

(4)比如咖啡,有两个具体的包装类

drink方法里面,没有更改drinkabledrink方法内部的逻辑,而且还对这个方法进行了功能的增强。这就是装饰器模式的核心理念。

(5)具体使用

可以看到,包装类要能装下一个drinkable的作用:就是能够装下具体类,或者具体包装类,从而实现对其功能的增强。在这里,coffee就是具体类,coffeeWithCup就是具体包装类,因为他们都实现了Drinkable接口,所以就可以被包装类持有,从而进行功能的增强。

上面的类,包装后的结果就是这样:

让我再来梳理一下这几个类的关系。

首先,有一个接口,表示某种东西"可以喝":Drinkable

然后,有一个具体的类,实现了这个接口:Coffee

然后,需要对这个具体类进行包装。因为包装可以有多种包装,即包装类可能会有多个,所以这里采用抽象类的形式:CoffeeDecorator。这个抽象类有一个特点,就是本身实现了Drinkable接口,然后还能装下一个Drinkable接口 。本身是Drinkable接口,其实也是为了能够"装下一个Drinkable"服务。因为如果本身没有实现Drinkable接口,那就不能被其他装饰类装下了。

然后,就是两个具体的包装类,这两个具体的包装类,继承了CoffeeDecorator。也可以把他们就理解成还是CoffeeDecorator

最后,在使用上。我们先创建了一个Coffee,然后包装它两层,最终实现如上图所示的效果。

我相信我已经解释的非常清晰了,你必须保证你完全理解上述装饰器模式的内容,特别是这个例子中几个类的关系。这对理解RxJava的装饰器模式至关重要。

RxJava的装饰器模式

我们都知道了,RxJava有两大角色,一是观察者Observer,二是被观察者Observable。RxJava的装饰器模式就是对这两大角色分别进行的包装。即对Observer有包装,对Observable也有包装。为什么要进行包装呢?很简单,想想装饰器模式的定义,就是为了增强原有功能。比如让RxJava引以为傲的线程切换功能,就是通过装饰器模式,外面套了一层,实现了线程切换,即"增强了原有功能",来实现的。

针对Observable的装饰器模式

我们先看对Observable的包装,下面是之前讲过的,一个标准的RxJava的使用示范

我们重点关注,这三个圆圈部分。这三个圆圈部分,都是被观察者。在这里,就是:创建一个被观察者,就套一层 。比如在这里,调用完Observable.create后,就生成了ObservableCreate对象。

然后调用了subscribeOn后,就生成了ObservableSubscribeOn对象,

调用了observeOn后,就生成了ObservableObserveOn对象。

这三个对象,都是包装类的子类,这个包装类就是:

在上文中我们说到,一个包装类,必须要实现一个接口,还要装下一个接口。那么这个神秘的接口,就是

点进去看接口的定义,就是

OK,看到这,你也许会不太清晰,不要紧,只要你理解了一开始我举的例子,那你一定能理解这里,因为这几乎就是照搬!不信?让我给你画个图

怎么样,是不是一模一样!

当然,有一个微小的不同。即Coffee是具体类,而ObservableCreate是包装类。这俩严格意义上来说,不是对应的,但不影响我们理解。什么?你想看严格对应的?OK,我来试图画一下

突然变得复杂了...,不过,在这个图中,你可以很清晰地看到,RxJava的Observable的装饰器模式的内核了。同时你会发现,ObservableOnSubscribe理应和Coffee对应,也实现ObservableSource接口。但实际上不是。它本身就是一个接口,它的定义是这样的

ObservableSource接口的定义,是这样的

你会发现,尽管参数不一样,但这俩接口,基本大差不差,基本上是一样的。那为什么要做这一层区分呢?我猜测是这样的:

为了区分不同subscribe的功能ObservableSourcesubscribe,突出的是订阅功能,即发起订阅,实现观察者和被观察者之间的订阅关系,这就是它的subscribe主要突出的作用。

ObservableOnSubscribesubscribe方法,突出的是发送事件功能。即被观察者的事件,如何发送到观察者,怎么发送到观察者。这是它侧重的功能。

让我们再看一下标准使用范例中,两个subscribe是怎么用的

这就是我理解的,这两个subscribe的区别

总结一下,Observable,是创建一次,包装一次 。创建后得到的类名,都是以Observable为前缀的,后缀,与创建方法相对应。比如调用了create方法,就创建了ObservableCreate包装类,调用了subscribeOn方法,就创建了ObservableSubscribeOn方法,以此类推。

针对Observer的装饰器模式

让我们看下Observer是如何被包装的。回归到标准使用范例中,当我们调用了这个subscribe方法时,会发生什么呢?

subscribe方法是抽象类Observable的方法,它实现了通用的逻辑,并调用了子类的subscribeActual方法,如下图

在这里,因为前面调用了observeOn方法,创建了ObservableObserveOn对象,所以在这里就是调用的ObservableObserveOn对象的subscribeActual方法,让我们进去看一下

这里的source,就是上一层包装类,即ObservableSubscribeOn。同时在这里,我们发现,它把Observer进行了一次包装,包装成了ObserveOnObserver类,然后传给了 ObservableSubscribeOn。然后我们进入ObservableSubscribeOnsubscribeActual方法

这里也对observer进行了包装,把它包装成了SubscribeOnObserver对象,然后我们进入到这里面

发现它还是一样,调用了sourcesubscribe方法

ObservableSubscribeOnsourceObservableCreate。所以让我们再进入到ObservableCreatesubscribeActual方法来看一下

还是老一套,但这里没有把Observer封装成类似CreateObserver这样的对象,而是CreateEmitter。然后它的source,就是最开始的ObservableOnSubscribe了。调用它的subscribe,就是调用这里:

好,以上我们非常详细地捋了一遍Observer的包装过程。我们发现,它是在被观察者调用subscribe的时候,一层一层地包装的。

让我们像上面一样,画图来捋一下它和我举的例子的对应关系

这里也类似Observable的包装过程,虽然总体思想相通,但是它还是没有完全和我举的例子对应,就是因为有两个接口,一是Observer接口,二是Emitter接口。但是呢,又是惊人的相似。这两个接口,实现的功能也是,大差不差。不信,你看Observer接口

然后,你再看Emitter接口

看到没,基本是一样的。而至于为什么要做这样的区分,我觉得原因之一,也是因为有不同的分工。Emitter侧重于"发送事件",而Observer侧重于作为事件的接受者,接收事件 。所以一般发送事件的时候,是调用Emitter的onNext,onComplete这些方法,然后再调用到Observer的onNext,onComplete这些方法。就相当于Emitter把Observer包了一层。那么在Emitter里面就可以进行功能的增强,比如可以判断线程是否释放了之类的。那是不是这样的呢?看源码!让我们进入Emitter的实现类:CreateEmitter中

发现,确实就是这样!

总结一下,Observer的包装,是调用subscribe方法的时候触发的,每次调用subscribe方法,都会对Observer包一层 。包装后的类名,和Observable类似,是以Observer作为后缀,比如经过observeOn后,就变成了ObserveOnObserver

什么线程切换,什么线程释放,都是在包装类里面实现的,比如我们看ObserveOnObserveronNext方法

这里就封装了线程切换的逻辑。其他的功能增强点,都是一样的道理,都在包装类中!

OK,接下来的时间,让我们再来梳理一下,Observable和Observer之间的对应关系

这就是RxJava的装饰器模式!怎么样,是不是非常清晰了!

四. 手写RxJava核心原理

下面,我们来个更牛的,你从来没有见过的,即手写一个RxJava核心原理逻辑!

基本介绍

举个很容易理解的例子,我先用文字描述清楚,然后再用代码写出来。

有两个角色:灯和人,人观察灯,当观察到灯灭的时候,采取一系列行动。比如人发现灯灭了,那就要开灯。开灯之前,要先擦手,然后手干燥了,再检查下电源,然后才能执行开灯的动作。这个例子,是不是很好理解!

但是这中间蕴藏着一个大坑!就是涉及了四个装饰器!哪四个呢?

"能实现擦手动作的人",以及"能实现检查电源动作的人"。

同时呢,电灯也有与之对应的两个装饰器

"能被擦完手的人观察的灯","能被人检查电源的灯"

这四个装饰器,对不对!

在前面的内容中,我们聊过,装饰器模式,有几个对应的角色,一个是接口,一个是具体类,一个是装饰器类

下面,我们代码来实现。

代码实现

(1)首先,关于灯的接口:

(2)然后,关于人的接口:

(其实我在有意模仿RxJava的风格)

然后,关于灯的具体类和人的具体类,我们就不写了。因为到时候可以直接new一个接口实现类,来代表具体类。(RxJava一般就是这么干的)

(3)然后是,关于灯的装饰器类:

哈哈哈,这回是不是很明显地能发现我在模仿RxJava。之前讲过,装饰器类有两个特点,一个是自己本身实现接口,然后还能装下一个接口 。这里的装饰器类是完全符合这个特点的。然后里面的subscribe方法和subscribeActual方法,也是模仿的RxJava的Observable

(4)然后,关于人的装饰器类:

也是符合"自己本身实现接口,还能装下一个接口"的特点。

以上,我们把人和灯,对应的接口和抽象装饰器类都介绍完了,我相信这里没有什么难度,你完全能理解。

下面,我们介绍具体的装饰器类,鉴于关于灯的装饰器有俩方法,看起来好像比较复杂(虽然其实很简单),为了防止你难以理解,我们先介绍关于人的具体装饰器类

(5)关于人的具体装饰器类:

装饰器类里面总共就只有一个方法,里面就两大块逻辑,一是实现功能的增强,二是调用原有的功能。这又和装饰器模式:不改变原有类的基础上实现功能的增强,相呼应。

欧克,下面我们来介绍一波

(6)关于灯的具体装饰器类:

乍一看,感觉好复杂,有种驴唇不对马嘴的感觉,但如果你认真看并且理解了我前面讲解的,关于观察者的包装过程,那这里就没有任何理解难度,因为这就是RxJava的被观察者的subscribeActual方法的核心执行逻辑!

subscribeActual方法里面,首先进行了功能的增强(就是把传来的观察者再包一层),然后调用了上一层被观察者的subscribe方法,继而调用到上一层被观察者的subscribeActual方法。

(7)在使用的时候,就是这样调用:

执行结果为:

以上,我们完成了手写RxJava的核心逻辑!现在,我们来复盘一下,它和RxJava是怎么对应的。先从使用这里看起

这是在使用的时候,我们写的例子和RxJava的对比,怎么样,是不是几乎一模一样!

然后我们来分析,在源码上,这两者的相似之处

上下两图对比,我们发现,基本上也是一样的!

到这里,关于RxJava的核心原理介绍,就介绍完了。

五. 总结

本篇文章,一开始我介绍了RxJava的观察者模式,然后我非常详细地介绍了RxJava中,Observable和Observer的装饰器模式怎么应用的,最后,手写了一个RxJava核心原理代码,加深了你的印象。

到此为止,本专栏的内容也全部结束了,希望能够对你有一些帮助,同时下个专栏,我将会详细地讲解Handler。如果你认为我的水平还可以,讲解内容还算比较干货,可以留个关注哦!

相关推荐
长风清留扬1 小时前
一篇文章了解何为 “大数据治理“ 理论与实践
大数据·数据库·面试·数据治理
Dnelic-1 小时前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记
Eastsea.Chen4 小时前
MTK Android12 user版本MtkLogger
android·framework
长亭外的少年11 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
周三有雨13 小时前
【面试题系列Vue07】Vuex是什么?使用Vuex的好处有哪些?
前端·vue.js·面试·typescript
爱米的前端小笔记13 小时前
前端八股自学笔记分享—页面布局(二)
前端·笔记·学习·面试·求职招聘
好学近乎知o13 小时前
解决sql字符串
面试
建群新人小猿13 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神15 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri
兰琛15 小时前
20241121 android中树结构列表(使用recyclerView实现)
android·gitee