关于RxJava最友好的文章
BernadineQL
8年前
<p><img src="https://simg.open-open.com/show/9dc9f8c76887df2a1a0bcf464898443a.jpg"></p> <p>RxJava到底是什么?让我们直接跳过官方那种晦涩的追求精确的定义,其实初学RxJava只要把握两点:观察者模式和异步,就基本可以熟练使用RxJava了。</p> <p>异步在这里并不需要做太多的解释,因为在概念和使用上,并没有太多高深的东西。大概就是你脑子里想能到的那些多线程,线程切换这些东西。我会在后面会讲解它的用法。</p> <p>我们先把观察者模式说清楚</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/15913da23b8f82e670138fa1bbbc6519.jpg"></p> <p>“按下开关,台灯灯亮”</p> <p>在这个事件中, <strong>台灯作为观察者,开关作为被观察者,台灯透过电线来观察开关的状态来并做出相应的处理</strong></p> <p style="text-align:center"><img src="https://simg.open-open.com/show/473b26fa5387771317430a1f80ef49b9.png"></p> <p>观察上图,其实已经很明了了,不过需要指出一下几点( <strong>对于下面理解RxJava很重要</strong> ):</p> <ul> <li>开关(被观察者)作为事件的 <strong>产生方</strong> (生产“开”和“关”这两个事件),是 <strong>主动</strong> 的,是整个开灯事理流程的 <strong>起点</strong> 。</li> <li>台灯(观察者)作为事件的 <strong>处理方</strong> (处理“灯亮”和“灯灭”这两个事件),是 <strong>被动</strong> 的,是整个开灯事件流程的 <strong>终点</strong> 。</li> <li>在起点和终点之间,即事件传递的过程中是可以被 <strong>加工,过滤,转换,合并等等</strong> 方式处理的(上图没有体现,后面对会讲到)。</li> </ul> <p>我必须苦口婆心的告诉你:我们总结的这三点对于我们理解RxJava非常重要。因为上述三条分别对应了RxJava中被观察者(Observable),观察者(Observer)和操作符的职能。而 <strong>观察者模式又是RxJava程序运行的骨架</strong> 。</p> <p>好了,我假设你已经完全理解了我上面讲述的东西。我们正式进入RxJava!</p> <p>RxJava也是基于观察者模式来组建自己的程序逻辑的,就是构建 <strong>被观察者(Observable),观察者(Observer/Subscriber)</strong> ,然后建立二者的订阅关系(就像那根电线,连接起台灯和开关)实现 <strong>观察</strong> ,在事件传递过程中还可以 <strong>对事件做各种处理</strong> 。</p> <p>Tips: Observer是观察者的接口, Subscriber是实现这个接口的抽象类,因此两个类都可以被当做观察者,由于Subscriber在Observe的基础上做了一些拓展,加入了新的方法,一般会更加倾向于使用Subscriber。</p> <h2><strong>创建被观察者</strong></h2> <ul> <li>正常模式:</li> </ul> <pre> <code class="language-java">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(); } });</code></pre> <p>这是最正宗的写法,创建了一个开关类,产生了五个事件,分别是:开,关,开,开,结束。</p> <ul> <li><strong>偷懒模式1</strong></li> </ul> <pre> <code class="language-java">Observable switcher=Observable.just("On","Off","On","On");</code></pre> <ul> <li><strong>偷懒模式2</strong></li> </ul> <pre> <code class="language-java">String [] kk={"On","Off","On","On"}; Observable switcher=Observable.from(kk);</code></pre> <p>偷懒模式是一种简便的写法,实际上也都是 <strong>被观察者</strong> 把那些信息"On","Off","On","On",包装成onNext("On")这样的事件依次发给 <strong>观察者</strong> ,当然,它自己补上了onComplete()事件。</p> <p>以上是最常用到的创建方式,好了,我们就创建了一个开关类。</p> <h2><strong>创建观察者</strong></h2> <ul> <li>正常模式</li> </ul> <pre> <code class="language-java">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) }</code></pre> <p>这也是比较常见的写法,创建了一个台灯类。</p> <ul> <li>偷懒模式(非正式写法)</li> </ul> <pre> <code class="language-java">Action1 light=new Action1<String>() { @Override public void call(String s) { Log.d("DDDDD","handle this---"+s) } }</code></pre> <p>之所以说它是非正式写法,是因为Action1是一个单纯的人畜无害的接口,和Observer没有啥关系,只不过它可以当做观察者来使,专门处理onNext 事件,这是一种为了简便偷懒的写法。当然还有Action0,Action2,Action3...,0,1,2,3分别表示call()这个方法能接受几个参数。如果你还不懂,可以暂时跳过。后面我也会尽量使用new Subscriber方式,创建正统的观察者,便于你们理解。</p> <h2><strong>订阅</strong></h2> <p>现在已经创建了观察者和被观察者,但是两者还没有联系起来</p> <pre> <code class="language-java">switcher.subscribe(light);</code></pre> <p>我猜你看到这里应该有疑问了,为什么是开关订阅了台灯?应该是台灯订阅了开关才对啊。卧槽,到底谁观察谁啊!!</p> <p>大家冷静,把刀放下,有话慢慢说,</p> <p>是这样的,台灯观察开关,逻辑是没错的,而且正常来看就应该是light.subscribe(switcher)才对,之所以“开关订阅台灯”,是为了保证 <strong>流式API调用风格</strong></p> <p>啥是 <strong>流式API调用风格?</strong></p> <pre> <code class="language-java">//这就是RxJava的流式API调用 Observable.just("On","Off","On","On") //在传递过程中对事件进行过滤操作 .filter(new Func1<String, Boolean>() { @Override public Boolean call(String s) { return s!=null; } }) .subscribe(mSubscriber);</code></pre> <p>上面就是一个非常简易的RxJava流式API的调用:同一个调用主体一路调用下来,一气呵成。</p> <p>由于被观察者产生事件,是事件的起点,那么开头就是用Observable这个主体调用来创建被观察者,产生事件,为了保证流式API调用规则,就直接让Observable作为唯一的调用主体,一路调用下去。</p> <p>一句话, <strong>背后的真实的逻辑依然是台灯订阅了开关,但是在表面上,我们让开关“假装”订阅了台灯,以便于保持流式API调用风格不变。</strong></p> <p>好了,现在分解动作都完成了,已经架构了一个基本的RxJava事件处理流程。</p> <p>我们再来按照观察者模式的运作流程和流式Api的写法复习一遍:</p> <p>流程图如下:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/bb0841303b37c862c098ec19ed8eb12d.png"></p> <p>结合流程图的相应代码实例如下:</p> <pre> <code class="language-java">//创建被观察者,是事件传递的起点 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) } );</code></pre> <p>嗯,基本上我们就把RxJava的骨架就讲完了,总结一下:</p> <ul> <li>创建被观察者,产生事件</li> <li>设置事件传递过程中的过滤,合并,变换等加工操作。</li> <li>订阅一个观察者对象,实现事件最终的处理。</li> </ul> <p>Tips: 当调用订阅操作(即调用Observable.subscribe()方法)的时候,被观察者才真正开始发出事件。</p> <p>现在开始讲异步操作?别着急,事件的产生起点和处理的终点我们都比较详细的讲解了,接下来我们好好讲讲事件传递过程中发生的那些事儿...</p> <h2><strong>RxJava的操作符</strong></h2> <p>即使你已经看了我上面那段讲解,Rxjava可能还打动不了你,没关系,事件产生的起点和消费的终点其实没那么吸引人,真正有意思的是事件传递过程中的那些鬼斧神工的操作。</p> <h3><strong>变换</strong></h3> <p><strong>Map操作</strong></p> <p>比如被观察者产生的事件中只有图片文件路径;,但是在观察者这里只想要bitmap,那么就需要 <strong>类型变换</strong> 。</p> <pre> <code class="language-java">Observable.create(new 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) } );</code></pre> <ul> <li>实际上在使用map操作时,new Func1() 就对应了类型的转你方向,String是原类型,Bitmap是转换后的类型。在call()方法中,输入的是原类型,返回转换后的类型</li> </ul> <p>你认真看完上面的代码就会觉得,何必在过程中变换类型呢?我直接在事件传递的终点,在观察者中变换就行咯。老实说,你这个想法没毛病,但实际上,上面写的代码是不合理的。</p> <p>我在代码中也提到,读取文件,创建bitmap可能是一个耗时操作,那么就应该在子线程中执行,主线程应该仅仅做展示。那么线程切换一般就会是比较复杂的事情了。但是在Rxjava中,是非常方便的。</p> <pre> <code class="language-java">Observable.create(new 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) } );</code></pre> <p>由上面的代码可以看到,使用操作符将事件处理逐步分解,通过线程调度为每一步设置不同的线程环境,完全解决了你线程切换的烦恼。可以说线程调度+操作符,才真正展现了RxJava无与伦比的魅力。</p> <p><strong>flatmap操作</strong></p> <p>先提出一个需求,查找一个学校每个班级的每个学生,并打印出来。</p> <p>如果用老办法:先读出所有班级的数据,循环每个班级。再循环中再读取每个班级中每个学生,然后循环打印出来。</p> <p>还是得说,这种想法,没毛病,就是嵌套得有点多。</p> <p>Rxjava说:我不是针对谁...</p> <pre> <code class="language-java">//创建被观察者,获取所有班级 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()) } );</code></pre> <p>好了,基本上按照RxJava的骨架搭起来就能完成需求。你说棒不棒??</p> <p>其实FlatMap是比较难懂的一个操作符,作为初学者其实会用就好,所以我推荐的对于FlatMap的解释是:将每个Observable产生的事件里的信息再包装成新的Observable传递出来,</p> <p>那么为什么FlatMap可以破除嵌套难题呢?</p> <p>就是因为FlatMap可以再次包装新的Observable,而每个Observable都可以使用from(T[])方法来创建自己,这个方法接受一个列表,然后将列表中的数据包装成一系列事件。</p> <h2><strong>异步(线程调度)</strong></h2> <p>异步是相对于主线程来讲的子线程操作,在这里我们不妨使用线程调度这个概念更加贴切。</p> <p>首先介绍一下RxJava的线程环境有哪些选项:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/9bce1d1c536c194ad0e2be34a763f184.png"></p> <p>在讲解Map操作符时,已经提到了线程调度,在这里我用更加简介的代码代替:</p> <pre> <code class="language-java">//new Observable.just()执行在新线程 Observable.create(new Observable.just(getFilePath()) //指定在新线程中创建被观察者 .subscribeOn(Schedulers.newThread()) //将接下来执行的线程环境指定为io线程 .observeOn(Schedulers.io()) //map就处在io线程 .map(mMapOperater) //将后面执行的线程环境切换为主线程, //但是这一句依然执行在io线程 .observeOn(AndroidSchedulers.mainThread()) //指定线程无效,但这句代码本身执行在主线程 .subscribeOn(Schedulers.io()) //执行在主线程 .subscribe(mSubscriber);</code></pre> <p>实际上线程调度只有subscribeOn()和observeOn()两个方法。对于初学者,只需要掌握两点:</p> <ul> <li> <p>subscribeOn()它指示Observable在一个指定的调度器上创建(只作用于被观察者创建阶段)。只能指定一次,如果指定多次则以第一次为准</p> </li> <li> <p>observeOn()指定在事件传递(加工变换)和最终被处理(观察者)的发生在哪一个调度器。可指定多次,每次指定完都在下一步生效。</p> </li> </ul> <p>线程调度掌握到这个程度,在入门阶段时绝对够用的了。</p> <h2><strong>结尾</strong></h2> <p>好了,对于RxJava整个入门文章到这里就完全结束了,现在再来回看RxJava,你会发现,它就是在观察者模式的骨架下,通过丰富的操作符和便捷的异步操作来完成对于复杂业务的处理。</p> <p>我相信你对于整个RxJava的骨架,以及执行流程应该有了相当的了解,现在就只需要多练习一下操作符的用法了。</p> <p> </p> <p> </p> <p>来自:https://zhuanlan.zhihu.com/p/23584382</p> <p> </p>