RxJava 系列 (三)RxJava lift原理

前言

理解lift原理有什么意义?

  • 可以理解Rxjava最核心的原理,看懂了lift你就看懂了Rxjava

lift是Rxjava操作符的基础原理,操作符是Rxjava功能如此丰富和好用的核心,理解了lift也就理解了Rxjava最核心的原理

  • 可以理解线程切换的原理,有助于灵活运用线程切换和调试线程相关的问题

线程切换也是用的操作符,所以原理也是lift

RxJava基本用法

  1. 创建 Observer
Observer<String> observer = new Observer<String>() {
    @Override
    public void onNext(String s) {
        Log.d(tag, "Item: " + s);
    }

    @Override
    public void onCompleted() {
        Log.d(tag, "Completed!");
    }

    @Override
    public void onError(Throwable e) {
        Log.d(tag, "Error!");
    }
};

Observer 需要实现三个方法,相当于定义了三种类型的事件

  • Subscriber 与 Observer的关系

Subscriber相当于增加了Subscription(订阅关系管理)功能的Observer
Subscription 包含两个方法:
unsubscribe();(取消订阅)
isUnsubscribed();(查询订阅关系)

Subscriber 还增加了一个onStart()方法:它会在 subscribe 刚开始,而事件还未发送之前被调用,可以用于做一些准备工作

实质上,在 RxJava 的 subscribe 过程中,Observer 也总是会先被转换成一个 Subscriber 再使用,所以为了统一,我们就统一以Subscriber来作为观察者,就不再提Observer了。

  1. 创建 Observable
Observable observable = Observable.create(new Observable.OnSubscribe<String>() {
    @Override
    public void call(Subscriber<? super String> subscriber) {
        subscriber.onNext("Hello");
        subscriber.onNext("Hi");
        subscriber.onNext("Aloha");
        subscriber.onCompleted();
    }
});

创建Observable对象时,会传入一个 OnSubscribe 对象,OnSubscribe对象会被存储在生成的 Observable 对象中。

OnSubscribe 对象的作用,就是拿到 subscriber对象,向subscriber 对象发送事件。
这里拿到观察者Subscriber对象,并调用Subscriber实现的三个方法,就是在向观察者发送事件

  1. Subscribe (订阅)
observable.subscribe(observer);
// 或者:
observable.subscribe(subscriber);

Observable.subscribe(Subscriber) 的内部实现是这样的(仅核心代码):

// 注意:这不是 subscribe() 的源码,而是将源码中与性能、兼容性、扩展性有关的代码剔除后的核心代码。
// 如果需要看源码,可以去 RxJava 的 GitHub 仓库下载。
public Subscription subscribe(Subscriber subscriber) {
    subscriber.onStart();
    onSubscribe.call(subscriber);
    return subscriber;
}

可以看到,subscriber() 做了3件事:

  1. 调用 Subscriber.onStart() 。这个方法在前面已经介绍过,是一个可选的准备方法。
  2. 调用 Observable 中的 OnSubscribe.call(Subscriber) 。在这里,事件发送的逻辑开始运行。从这也可以看出,在 RxJava 中, Observable 并不是在创建的时候就立即开始发送事件,而是在它被订阅的时候,即当 subscribe() 方法执行的时候。
  3. 将传入的 Subscriber 作为 Subscription 返回。这是为了方便进行订阅关系管理,比如 unsubscribe().

操作符-对事件序列进行变换

所谓变换,就是将事件序列中的对象或整个序列进行加工处理,转换成不同的事件或事件序列。概念说着总是模糊难懂的,来看 例子。

举个例子:map()

Observable.just("images/logo.png") // 输入类型 String
    .map(new Func1<String, Bitmap>() {
        @Override
        public Bitmap call(String filePath) { // 参数类型 String
            return getBitmapFromPath(filePath); // 返回类型 Bitmap
        }
    })
    .subscribe(new Action1<Bitmap>() {
        @Override
        public void call(Bitmap bitmap) { // 参数类型 Bitmap
            showBitmap(bitmap);
        }
    });

map(): 对事件对象的直接变换。

变换的原理:lift()

这些变换虽然功能各有不同,但实质上都是针对事件序列的处理和再发送。而在 RxJava 的内部,它们是基于同一个基础的变换方法: lift(Operator)。首先看一下 lift() 的内部实现(仅核心代码):

// 注意:这不是 lift() 的源码,而是将源码中与性能、兼容性、扩展性有关的代码剔除后的核心代码。
// 如果需要看源码,可以去 RxJava 的 GitHub 仓库下载。
public <R> Observable<R> lift(Operator<? extends R, ? super T> operator) {
    return Observable.create(new OnSubscribe<R>() {
        @Override
        public void call(Subscriber subscriber) {
            Subscriber newSubscriber = operator.call(subscriber);
            newSubscriber.onStart();
            onSubscribe.call(newSubscriber);
        }
    });
}

先不用关心细节,我们先知道 lift是接受一个operator参数,返回一个新的Observable对象。
在分析 lift() 的内部实现之前,我们先看一下加上操作符的一次调用的完整过程

一次包含操作符的调用的完整过程

Observable.just("images/logo.png") // 输入类型 String
    .map(new Func1<String, Bitmap>() {
        @Override
        public Bitmap call(String filePath) { // 参数类型 String
            return getBitmapFromPath(filePath); // 返回类型 Bitmap
        }
    })
    .subscribe(new Action1<Bitmap>() {
        @Override
        public void call(Bitmap bitmap) { // 参数类型 Bitmap
            showBitmap(bitmap);
        }
    });

做了三步:
1.生成observable对象
2.在observable对象上调用了map方法
3.在map方法的返回值上调用了.subscribe方法

问题:1.map方法内部做了什么? 2.map方法的返回值是个什么?

回答两个问题

因为map()内部不是直接调用的lift方法(跟lift的原理一样,只是没有直接使用lift方法),所以我们以take()操作符的源码为例,来看方法调用。


回答两个问题:

  1. take方法内部很简单,就是调用了lift方法
    2.返回值就是lift方法的返回值,是个新new的observable对象。

将示例稍作修改

        Observable observable1 = Observable.just("images/logo.png"); // 输入类型 String
        
        Observable observable2 = observable1.map(new Func1<String, Bitmap>() {
                    @Override
                    public Bitmap call(String filePath) { // 参数类型 String
                        return getBitmapFromPath(filePath); // 返回类型 Bitmap
                    }
                });
        
        observable2.subscribe(new Action1<Bitmap>() {
                    @Override
                    public void call(Bitmap bitmap) { // 参数类型 Bitmap
                        showBitmap(bitmap);
                    }
                });
observable2.subscribe 分析

回顾前面subscribe()的内部实现,我们发现observable2里的OnSubscribe对象的call方法会被调用。而observable2就是lift方法返回的Observable对象, observable2里的onSubscribe对象就是lift的核心重点。

observable2里的OnSubscribe对象 —lift的核心重点

observable2对象里面通过observable1对象的onSubscribe.call(newSubscriber)达到通知observable1目的。
因为observable1对象是我们初始的Observable对象,它的onSubscribe.call会发送事件到newSubscriber。
newSubscriber是operator.call(subscriber)返回的,newSubscriber做了两件事:
1.进行事件的变换操作。newSubscriber能拿到初始的事件,可以进行转换操作,这也是操作符发生效力的地方,不同的操作符的作用就是对事件进行不同的转换。
2.转发给subscriber,newSubscriber有subscriber的引用,可以将转换后的事件转发给subscriber,也就是最终的订阅者。
lift方法返回的observable2对象在调用链的中间起到了一个中转的作用,这就是lift原理的核心。

operator.call内部通过传入一个subscriber返回一个newSubscriber,newSubscriber能达到事件转换和转发的目的。
是如何做到的?
我们来举个例子

Operator的一个例子

多个操作符的情况

多个操作符相当于中间经过了多层中转,原理都一样

关于事件发送的触发

结合上面多个操作符的图,强调一下:

事件发送的触发是从调用subscribe()方法后开始的,前面哪怕调用了N多的操作符方法只要还没有调用subscribe()方法其实并没有触发事件的发送。

事件发送之前有一个从下往上通知的过程,当subscribe()方法被调用之后,先是通过observable2对象里调用observable1对象的onSubscribe.call通知observable1对象,如果observable1对象不是最开始发送事件的Observable对象(多个操作符的情况),那么同样的还会继续往上通知,直到通知到初始的Observable对象,才会开始事件的发送。

所以RxJava是 先从下往上通知,然后再从上往下发送事件

参考文献

给 Android 开发者的 RxJava 详解 --扔物线
http://gank.io/post/560e15be2dca930e00da1083

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容

  • 我从去年开始使用 RxJava ,到现在一年多了。今年加入了 Flipboard 后,看到 Flipboard 的...
    Jason_andy阅读 5,381评论 7 62
  • 前言我从去年开始使用 RxJava ,到现在一年多了。今年加入了 Flipboard 后,看到 Flipboard...
    占导zqq阅读 9,121评论 6 151
  • 最近项目里面有用到Rxjava框架,感觉很强大的巨作,所以在网上搜了很多相关文章,发现一片文章很不错,今天把这篇文...
    Scus阅读 6,794评论 2 50
  • Github:https://github.com/ReactiveX/RxJavahttps://github....
    才兄说阅读 1,585评论 2 10
  • 心中的暖意是从上次见你一面的时候开始的,因此我许久都不能为之确定心里仿佛出现了一个大洞的心情是由于你而造成的,还是...
    程程有趣阅读 848评论 7 12