关于RxJava最友好的文章

本文首发于掘金-关于RxJava最友好的文章

RxJava到底是什么?让我们直接跳过官方那种晦涩的追求精确的定义,其实初学RxJava只要把握两点:观察者模式异步,就基本可以熟练使用RxJava了。

异步在这里并不需要做太多的解释,因为在概念和使用上,并没有太多高深的东西。大概就是你脑子里想能到的那些多线程,线程切换这些东西。我会在后面会讲解它的用法。

我们先把观察者模式说清楚

“按下开关,台灯灯亮”

在这个事件中,台灯作为观察者,开关作为被观察者,台灯透过电线来观察开关的状态来并做出相应的处理

观察上图,其实已经很明了了,不过需要指出一下几点(对于下面理解RxJava很重要):

  • 开关(被观察者)作为事件的产生方(生产“开”和“关”这两个事件),是主动的,是整个开灯事理流程的起点
  • 台灯(观察者)作为事件的处理方(处理“灯亮”和“灯灭”这两个事件),是被动的,是整个开灯事件流程的终点
  • 在起点和终点之间,即事件传递的过程中是可以被加工,过滤,转换,合并等等方式处理的(上图没有体现,后面对会讲到)。

我必须苦口婆心的告诉你:我们总结的这三点对于我们理解RxJava非常重要。因为上述三条分别对应了RxJava中被观察者(Observable),观察者(Observer)和操作符的职能。而观察者模式又是RxJava程序运行的骨架

好了,我假设你已经完全理解了我上面讲述的东西。我们正式进入RxJava!

RxJava也是基于观察者模式来组建自己的程序逻辑的,就是构建被观察者(Observable),观察者(Observer/Subscriber),然后建立二者的订阅关系(就像那根电线,连接起台灯和开关)实现观察,在事件传递过程中还可以对事件做各种处理

Tips: Observer是观察者的接口, Subscriber是实现这个接口的抽象类,因此两个类都可以被当做观察者,由于Subscriber在Observe的基础上做了一些拓展,加入了新的方法,一般会更加倾向于使用Subscriber。


创建被观察者

  • 正常模式:
 Observable switcher=Observable.create(new Observable.OnSubscribe<String>(){

            @Override
            public void call(Subscriber<? super String> subscriber) {
                subscriber.onNext("On");
                subscriber.onNext("Off");
                subscriber.onNext("On");
                subscriber.onNext("On");
                subscriber.onCompleted();
            }
        });
  

这是最正宗的写法,创建了一个开关类,产生了五个事件,分别是:开,关,开,开,结束。

  • 偷懒模式1
Observable switcher=Observable.just("On","Off","On","On");

  • 偷懒模式2
String [] kk={"On","Off","On","On"};
Observable switcher=Observable.from(kk);  

偷懒模式是一种简便的写法,实际上也都是被观察者把那些信息"On","Off","On","On",包装成onNext("On")这样的事件依次发给观察者,当然,它自己补上了onComplete()事件。

以上是最常用到的创建方式,好了,我们就创建了一个开关类。


创建观察者

  • 正常模式
 Subscriber light=new Subscriber<String>() {
            @Override
            public void onCompleted() {
                //被观察者的onCompleted()事件会走到这里;
                Log.d("DDDDDD","结束观察...\n");
            }

            @Override
            public void onError(Throwable e) {
                    //出现错误会调用这个方法
            }
            @Override
            public void onNext(String s) {
                //处理传过来的onNext事件
                Log.d("DDDDD","handle this---"+s)
            }

这也是比较常见的写法,创建了一个台灯类。

  • 偷懒模式(非正式写法)
        Action1 light=new Action1<String>() {
                @Override
                public void call(String s) {
                    Log.d("DDDDD","handle this---"+s)
                }
            }

之所以说它是非正式写法,是因为Action1是一个单纯的人畜无害的接口,和Observer没有啥关系,只不过它可以当做观察者来使,专门处理onNext 事件,这是一种为了简便偷懒的写法。当然还有Action0,Action2,Action3...,0,1,2,3分别表示call()这个方法能接受几个参数。如果你还不懂,可以暂时跳过。后面我也会尽量使用new Subscriber方式,创建正统的观察者,便于你们理解。

订阅

现在已经创建了观察者和被观察者,但是两者还没有联系起来

switcher.subscribe(light);

我猜你看到这里应该有疑问了,为什么是开关订阅了台灯?应该是台灯订阅了开关才对啊。卧槽,到底谁观察谁啊!!

大家冷静,把刀放下,有话慢慢说,

是这样的,台灯观察开关,逻辑是没错的,而且正常来看就应该是light.subscribe(switcher)才对,之所以“开关订阅台灯”,是为了保证流式API调用风格

啥是流式API调用风格

//这就是RxJava的流式API调用
Observable.just("On","Off","On","On")
        //在传递过程中对事件进行过滤操作
         .filter(new Func1<String, Boolean>() {
                    @Override
                    public Boolean call(String s) {
                        return s!=null;
                    }
                })
        .subscribe(mSubscriber);

上面就是一个非常简易的RxJava流式API的调用:同一个调用主体一路调用下来,一气呵成。

由于被观察者产生事件,是事件的起点,那么开头就是用Observable这个主体调用来创建被观察者,产生事件,为了保证流式API调用规则,就直接让Observable作为唯一的调用主体,一路调用下去。

一句话,背后的真实的逻辑依然是台灯订阅了开关,但是在表面上,我们让开关“假装”订阅了台灯,以便于保持流式API调用风格不变。

好了,现在分解动作都完成了,已经架构了一个基本的RxJava事件处理流程。

我们再来按照观察者模式的运作流程和流式Api的写法复习一遍:

流程图如下:

结合流程图的相应代码实例如下:

//创建被观察者,是事件传递的起点
Observable.just("On","Off","On","On")
        //这就是在传递过程中对事件进行过滤操作
         .filter(new Func1<String, Boolean>() {
                    @Override
                    public Boolean call(String s) {
                        return s!=null;
                    }
                })
        //实现订阅
        .subscribe(
                //创建观察者,作为事件传递的终点处理事件    
                  new Subscriber<String>() {
                        @Override
                        public void onCompleted() {
                            Log.d("DDDDDD","结束观察...\n");
                        }
            
                        @Override
                        public void onError(Throwable e) {
                            //出现错误会调用这个方法
                        }
                        @Override
                        public void onNext(String s) {
                            //处理事件
                            Log.d("DDDDD","handle this---"+s)
                        }
        );

嗯,基本上我们就把RxJava的骨架就讲完了,总结一下:

  • 创建被观察者,产生事件
  • 设置事件传递过程中的过滤,合并,变换等加工操作。
  • 订阅一个观察者对象,实现事件最终的处理。

Tips: 当调用订阅操作(即调用Observable.subscribe()方法)的时候,被观察者才真正开始发出事件。


现在开始讲异步操作?别着急,事件的产生起点和处理的终点我们都比较详细的讲解了,接下来我们好好讲讲事件传递过程中发生的那些事儿...

RxJava的操作符

即使你已经看了我上面那段讲解,Rxjava可能还打动不了你,没关系,事件产生的起点和消费的终点其实没那么吸引人,真正有意思的是事件传递过程中的那些鬼斧神工的操作。

由于篇幅的限制,我只讲两三个操作,其他的操作请看我的RxJava操作的Demo记得在github给我点star或者follow一下,不然我就坐在地上不起来,哼

变换

Map操作

比如被观察者产生的事件中只有图片文件路径;,但是在观察者这里只想要bitmap,那么就需要类型变换

  Observable.just(getFilePath()
            //使用map操作来完成类型转换
            .map(new Func1<String, Bitmap>() {
              @Override
              public Bitmap call(String s) {
                //显然自定义的createBitmapFromPath(s)方法,是一个极其耗时的操作
                  return createBitmapFromPath(s);
              }
          })
            .subscribe(
                 //创建观察者,作为事件传递的终点处理事件    
                  new Subscriber<Bitmap>() {
                        @Override
                        public void onCompleted() {
                            Log.d("DDDDDD","结束观察...\n");
                        }
            
                        @Override
                        public void onError(Throwable e) {
                            //出现错误会调用这个方法
                        }
                        @Override
                        public void onNext(Bitmap s) {
                            //处理事件
                            showBitmap(s)
                        }
                    );
            
  • 实际上在使用map操作时,new Func1<String, Bitmap>() 就对应了类型的转你方向,String是原类型,Bitmap是转换后的类型。在call()方法中,输入的是原类型,返回转换后的类型

你认真看完上面的代码就会觉得,何必在过程中变换类型呢?我直接在事件传递的终点,在观察者中变换就行咯。老实说,你这个想法没毛病,但实际上,上面写的代码是不合理的。

我在代码中也提到,读取文件,创建bitmap可能是一个耗时操作,那么就应该在子线程中执行,主线程应该仅仅做展示。那么线程切换一般就会是比较复杂的事情了。但是在Rxjava中,是非常方便的。

 Observable.just(getFilePath()
           //指定了被观察者执行的线程环境
          .subscribeOn(Schedulers.newThread())
          //将接下来执行的线程环境指定为io线程
          .observeOn(Schedulers.io())
            //使用map操作来完成类型转换
            .map(new Func1<String, Bitmap>() {
              @Override
              public Bitmap call(String s) {
                //显然自定义的createBitmapFromPath(s)方法,是一个极其耗时的操作
                  return createBitmapFromPath(s);
              }
          })
            //将后面执行的线程环境切换为主线程
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                 //创建观察者,作为事件传递的终点处理事件    
                  new Subscriber<Bitmap>() {
                        @Override
                        public void onCompleted() {
                            Log.d("DDDDDD","结束观察...\n");
                        }
            
                        @Override
                        public void onError(Throwable e) {
                            //出现错误会调用这个方法
                        }
                        @Override
                        public void onNext(Bitmap s) {
                            //处理事件
                            showBitmap(s)
                        }
                    );
            

由上面的代码可以看到,使用操作符将事件处理逐步分解,通过线程调度为每一步设置不同的线程环境,完全解决了你线程切换的烦恼。可以说线程调度+操作符,才真正展现了RxJava无与伦比的魅力。

flatmap操作

先提出一个需求,查找一个学校每个班级的每个学生,并打印出来。

如果用老办法:先读出所有班级的数据,循环每个班级。再循环中再读取每个班级中每个学生,然后循环打印出来。

还是得说,这种想法,没毛病,就是嵌套得有点多。

Rxjava说:我不是针对谁...

//创建被观察者,获取所有班级
 Observable.from(getSchoolClasses())
                .flatMap(new Func1<SingleClass, Observable<Student>>() {
                    @Override
                    public Observable<Student> call(SingleClass singleClass) {
                        //将每个班级的所有学生作为一列表包装成一列Observable<Student>,将学生一个一个传递出去
                        return Observable.from(singleClass.getStudents());
                    }
                })
                .subscribe(
                //创建观察者,作为事件传递的终点处理事件    
                  new Subscriber<Student>() {
                        @Override
                        public void onCompleted() {
                            Log.d("DDDDDD","结束观察...\n");
                        }
            
                        @Override
                        public void onError(Throwable e) {
                            //出现错误会调用这个方法
                        }
                        @Override
                        public void onNext(Student student) {
                            //接受到每个学生类
                            Log.d("DDDDDD",student.getName())
                        }
                    );

好了,基本上按照RxJava的骨架搭起来就能完成需求。你说棒不棒??

其实FlatMap是比较难懂的一个操作符,作为初学者其实会用就好,所以我推荐的对于FlatMap的解释是:将每个Observable产生的事件里的信息再包装成新的Observable传递出来,

那么为什么FlatMap可以破除嵌套难题呢?

就是因为FlatMap可以再次包装新的Observable,而每个Observable都可以使用from(T[])方法来创建自己,这个方法接受一个列表,然后将列表中的数据包装成一系列事件。


异步(线程调度)

异步是相对于主线程来讲的子线程操作,在这里我们不妨使用线程调度这个概念更加贴切。

首先介绍一下RxJava的线程环境有哪些选项:

在讲解Map操作符时,已经提到了线程调度,在这里我用更加简介的代码代替:

            //new Observable.just()执行在新线程
Observable.just(getFilePath()
           //指定在新线程中创建被观察者
          .subscribeOn(Schedulers.newThread())
          //将接下来执行的线程环境指定为io线程
          .observeOn(Schedulers.io())
            //map就处在io线程
          .map(mMapOperater)
            //将后面执行的线程环境切换为主线程,
            //但是这一句依然执行在io线程
          .observeOn(AndroidSchedulers.mainThread())
          //指定线程无效,但这句代码本身执行在主线程
          .subscribeOn(Schedulers.io())
          //执行在主线程
          .subscribe(mSubscriber);

实际上线程调度只有subscribeOn()和observeOn()两个方法。对于初学者,只需要掌握两点:

  • subscribeOn()它指示Observable在一个指定的调度器上创建(只作用于被观察者创建阶段)。只能指定一次,如果指定多次则以第一次为准

  • observeOn()指定在事件传递(加工变换)和最终被处理(观察者)的发生在哪一个调度器。可指定多次,每次指定完都在下一步生效。

线程调度掌握到这个程度,在入门阶段时绝对够用的了。

结尾

好了,对于RxJava整个入门文章到这里就完全结束了,现在再来回看RxJava,你会发现,它就是在观察者模式的骨架下,通过丰富的操作符和便捷的异步操作来完成对于复杂业务的处理

我相信你对于整个RxJava的骨架,以及执行流程应该有了相当的了解,现在就只需要多练习一下操作符的用法了。

本文没有介绍太多的操作符,很多没来得及介绍的操作符的用法实例都放在github上的RxJavaDemo项目上了,后期还会继续加上更多操作符的使用,欢迎大家上去看看,对照代码,手机运行一下。

大家多给点star!!顺便follow一下,接下来,我也会慢慢整理出一些别的有用的项目分享给大家。

勘误

暂无

后记

如果你还有不太理解,或者觉得文章在某些地方还有提升的空间,欢迎在下方留言,帮助我一起把这篇文章改得更加简单,生动,透彻,实用。这也是我写Blog一直追求的目标。

而且,我希望这篇文章能成为RxJava入门上手最好的文章。

附源码

文章涉及和未涉及的代码范例

个人Github主页

star ~~

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

推荐阅读更多精彩内容