彻底了解RxJava(一)基础知识

原文链接:http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/
RxJava是目前在Android开发者中新兴热门的函数库。唯一的问题是刚开始接触时会感到较难理解。函数响应式编程对于“外面世界”来的开发人员而言是很难理解的,但一旦理解了它,你会感觉真是太棒了。
我将介绍RxJava的一些知识,这系列文章(四部分)的目标是把你领进RxJava的大门。我不会解释所有相关的知识点(我也做不到),我只想引起你对RxJava的兴趣并知道它是如何工作的。

基础知识

响应式代码的基本组成部分是Observables和Subscribers(事实上Observer才是最小的构建块,但实践中使用最多的是Subscriber,因为Subscriber才是和Observables的对应的。)。Observable发送消息,而Subscriber则用于消费消息。
消息的发送是有固定模式的。Observable可以发送任意数量的消息(包括空消息),当消息被成功处理或者出错时,流程结束。Observable会调用它的每个Subscriber的Subscriber.onNext()函数,并最终以Subscriber.onComplete()或者Subscriber.onError()结束。
这看起来像标准的观察者模式, 但不同的一个关键点是:Observables一般只有等到有Subscriber订阅它,才会开始发送消息(术语上讲就是热启动Observable和冷启动Observable。热启动Observable任何时候都会发送消息,即使没有任何观察者监听它。冷启动Observable只有在至少有一个订阅者的时候才会发送消息(我的例子中都是只有一个订阅者)。这个区别对于开始学习RxJava来说并不重要。)。换句话说,如果没有订阅者观察它,那么将不会起什么作用。

Hello, World!

让我们以一个具体例子来实际看看这个框架。首先,我们创建一个基本的Observable:

Observable<String> myObservable = Observable.create(
    new Observable.OnSubscribe<String>() {
        @Override
        public void call(Subscriber<? super String> sub) {
            sub.onNext("Hello, world!");
            sub.onCompleted();
        }
    }
);

我们的Observable发送“Hello,world!”消息然后完成。现在让我们创建Subscriber来消费这个数据:

Subscriber<String> mySubscriber = new Subscriber<String>() {
    @Override
    public void onNext(String s) { System.out.println(s); }

    @Override
    public void onCompleted() { }

    @Override
    public void onError(Throwable e) { }
};

上面代码所做的工作就是打印由Observable发送的字符串。现在我们有了myObservable和mySubscriber,就可以通过subscribe()函数把两者关联起来:

myObservable.subscribe(mySubscriber);
// Outputs "Hello, world!"

当订阅完成,myObservable将调用subscriber的onNext()和onComplete()函数,最终mySubscriber打印“Hello, world!”然后终止。

更简洁的代码

上面为了打印“Hello, world!”写了大量的样板代码,目的是为了让你详细了解到底发生了什么。RxJava提供了很多快捷方式来使编码更简单。
首先让我们简化Observable,RxJava为常见任务提供了很多内建的Observable创建函数。在以下这个例子中,Observable.just()发送一个消息然后完成,功能类似上面的代码(严格来说,Observable.just()函数跟我们原来的代码并不完全一致,但在本系列第三部分之前我不会说明原因):

Observable<String> myObservable =
    Observable.just("Hello, world!");

接下来,让我们处理Subscriber不必要的样板代码。如果我们不关心onCompleted()或者onError()的话,那么可以使用一个更简单的类来定义onNext()期间要完成什么功能:

Action1<String> onNextAction = new Action1<String>() {
    @Override
    public void call(String s) {
        System.out.println(s);
    }
};

Actions可以定义Subscriber的每一个部分,Observable.subscribe()函数能够处理一个,两个或者三个Action参数,分别表示onNext(),onError()和onComplete()函数。上面的Subscriber现在如下所示:

myObservable.subscribe(onNextAction, onErrorAction, onCompleteAction);

然而,现在我们不需要onError()和onComplete()函数,因此只需要第一个参数:

myObservable.subscribe(onNextAction);
// Outputs "Hello, world!"

现在,让我们把上面的函数调用链接起来从而去掉临时变量:

Observable.just("Hello, world!")
    .subscribe(new Action1<String>() {
        @Override
        public void call(String s) {
              System.out.println(s);
        }
    });

最后,我们使用Java 8的lambdas表达式来去掉丑陋的Action1代码:

Observable.just("Hello, world!")
    .subscribe(s -> System.out.println(s));

如果你在Android平台上(因此不能使用Java 8),那么我严重推荐使用retrolambda,它将极大的减少代码的冗余度。

变换

让我们来点刺激的。假如我想把我的签名拼接到“Hello, world!的输出中。一个可行的办法是改变Observable:

Observable.just("Hello, world! -Dan")
    .subscribe(s -> System.out.println(s));

当你有权限控制Observable时这么做是可行的,但不能保证每次都可以这样,如果你使用的是别人的函数库呢?另外一个可能的问题是:如果项目中在多个地方使用Observable,但只在某个地方需要增加签名,这时怎么办?
我们尝试修改Subscriber如何呢?

Observable.just("Hello, world!")
    .subscribe(s -> System.out.println(s + " -Dan"));

这个解决方案也不尽如人意,不过原因不同于上面:我想Subscribers尽可能的轻量级,因为我可能要在主线程中运行它。从更概念化的层面上讲,Subscribers是用于被动响应的,而不是主动发送消息使其他对象发生变化。 如果可以通过某些中间步骤来对上面的“Hello, world!”进行转换,那岂不是很酷?

Operators简介

接下来我们将介绍如何解决消息转换的难题:使用Operators。Operators在消息发送者Observable和消息消费者Subscriber之间起到操纵消息的作用。RxJava拥有大量的opetators,但刚开始最好还是从一小部分开始熟悉。 这种情况下,map() operator可以被用于将已被发送的消息转换成另外一种形式:

Observable.just("Hello, world!")
    .map(new Func1<String, String>() {
        @Override
        public String call(String s) {
            return s + " -Dan";
        }
    })
    .subscribe(s -> System.out.println(s));

同样的,我们可以使用Java 8的lambdas表达式来简化代码:

Observable.just("Hello, world!")
    .map(s -> s + " -Dan")
    .subscribe(s -> System.out.println(s));

很酷吧?我们的map() operator本质上是一个用于转换消息对象的Observable。我们可以级联调用任意多个的map()函数,一层一层地将初始消息转换成Subscriber需要的数据形式。

map()更多的解释

map()函数有趣的一点是:它不需要发送和原始的Observable一样的数据类型。假如我的Subscriber不想直接输出原始的字符串,而是想输出原始字符串的hash值:

Observable.just("Hello, world!")
    .map(new Func1<String, Integer>() {
        @Override
        public Integer call(String s) {
            return s.hashCode();
        }
    })
    .subscribe(i -> System.out.println(Integer.toString(i)));

有趣的是,我们的原始输入是字符串,但Subscriber最终收到的是Integer类型。同样的,我们可以使用lambdas简化代码如下:

Observable.just("Hello, world!")
    .map(s -> s.hashCode())
    .subscribe(i -> System.out.println(Integer.toString(i)));

正如我前面说过的,我希望Subscriber做尽量少的工作,我们可以把hash值转换成字符串的操作移动到一个新的map()函数中:

Observable.just("Hello, world!")
    .map(s -> s.hashCode())
    .map(i -> Integer.toString(i))
    .subscribe(s -> System.out.println(s));

你不想瞧瞧这个吗?我们的Observable和Subscriber变回了原来的代码。我们只是在中间添加了一些变换的步骤,我甚至可以把我的签名转换添加回去:

Observable.just("Hello, world!")
    .map(s -> s + " -Dan")
    .map(s -> s.hashCode())
    .map(i -> Integer.toString(i))
    .subscribe(s -> System.out.println(s));

那又怎样?

到这里你可能会想“为了得到简单的代码有很多聪明的花招”。确实,这是一个简单的例子,但有两个概念你需要理解:
关键概念#1:Observable和Subscriber能完成任何事情。
放飞你的想象,任何事情都是可能的。 你的Observable可以是一个数据库查询,Subscriber获得查询结果然后将其显示在屏幕上。你的Observable可以是屏幕上的一个点击,Subscriber响应该事件。你的Observable可以从网络上读取一个字节流,Subscriber将其写入本地磁盘中。 这是一个可以处理任何事情的通用框架。
关键概念#2:Observable和Subscriber与它们之间的一系列转换步骤是相互独立的。
我们可以在消息发送者Observable和消息消费者Subscriber之间加入任意多个想要的map()函数。这个系统是高度可组合的:它很容易对数据进行操纵。只要operators符合输入输出的数据类型,那么我可以得到一个无穷尽的调用链(好吧,并不是无穷尽的,因为总会到达物理机器的极限的,但你知道我想表达的意思)。
结合两个关键概念,你可以看到一个有极大潜能的系统。然而到这里我们只介绍了一个operator:map(),这严重地限制了我们的可能性。在本系列的第二部分,我们将详细介绍RxJava中大量可用的operators。

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

推荐阅读更多精彩内容