用工厂流水线的方式来理解 RxJava 的概念
zhuqin1202
8年前
<h2>为什么另写一篇 RxJava 的文章?</h2> <p>已经有很多 <a href="/misc/goto?guid=4958865750209621132">RxJava</a> 的文章通过例子阐述了什么是 RxJava 以及怎么去用,但它们大多数只有代码。虽然也会通过类比来解释,例如最出名的就是「流」。通常情况下代码能完美地让人理解(我们都是程序员,对吧?),但是 RxJava 十分不同于以往的 Android 开发。在最开始时通过代码是很难让人理解的,用「流」来类比并不足够,即使是<a href="/misc/goto?guid=4959654058854309011"> marbles </a>的例子也还远远不够。我可以保证自己能理解,但对于别人,老实说,难道你们不需要更多结合实际的例子?难道你们不想在脑海中举一个例子来让自己更好地理解 RxJava 吗?我做了,并且我想和你们分享。</p> <h2>工厂流水线</h2> <p>好吧,我说谎了。为了理解 RxJava,我在脑海里举了不仅仅一个例子。例如我尝试观察动物园笼子的动物,尝试观察河流里的鱼,也尝试去观察蝙蝠侠里的犯罪(额,这不是现实生活中的,但不失为一个很好的例子)。但我还是认为工厂流水线是最好的例子。</p> <p><img src="https://simg.open-open.com/show/59a0c3cb1fe52a854b94bca35463e1c4.jpg"></p> <h3>需求</h3> <p>我们先想象下,我们需要写一款应用来展示动物们的信息,并且现在我需要在新的界面实现以下功能:</p> <ul> <li>三份关于猫的信息</li> <li>每份都是唯一的</li> <li>每份都应该少于 300 字符</li> <li>每份都配张猫的图片</li> </ul> <h3>启动流水线!</h3> <p>我们尝试通过流水线上的工人们的帮助实现那些功能。</p> <p>1. 首先我们需要启动产品处理进程。仅仅有流水线是不足够的 - 还需要有人去启动它。例如一个对结果感兴趣的产品经理</p> <p><img src="https://simg.open-open.com/show/502be245ef2bcd2b4106c1358fc5031e.jpg"></p> <p>2. 现在我们需要随机获得有关猫的信息。但怎么做呢?幸运的是这里有<a href="/misc/goto?guid=4959672016952941616"> API </a>能让我们很容易做到!真的是巧合么?好吧,我们先通过 <em>GET</em> 方法获取这些信息。</p> <p><img src="https://simg.open-open.com/show/9b9ddc4df6944031a2edf0fb81ce7fb3.jpg"></p> <p>3. 现在我们需要处理来自 API 的响应。它是由 HTTP 状态码和一列有关猫的信息组成。我们并不需要状态码所以我们首先去除它并将信息列表传给下一个工人。</p> <p><img src="https://simg.open-open.com/show/4c39f2900200346740bab97ebdb118bd.jpg"></p> <p>4. 下一件要做的事就是将信息列表的信息一个个抽离出来。为什么要这么做?因为这样做方便下游的工人操作单个字符串(例如检查字符串是否过长)。</p> <p><img src="https://simg.open-open.com/show/becf48e3d87fb736e7ea066f80ddaa32.jpg"></p> <p>5. 每条信息都是唯一的。下一个工人的任务是清除重复项。</p> <p><img src="https://simg.open-open.com/show/1e9f3185574e127a325ea8cd052f4def.jpg"></p> <p>6. 每条信息都不能太长(少于 300 字符)</p> <p><img src="https://simg.open-open.com/show/cbc58a7392109ad58d07ec9e9cc3c6c3.jpg"></p> <p>7. 现在我们的信息是唯一的且长度也符合要求可数量太多了而我们仅仅需要 3 份。所以下一个工人应该清除多余的信息。</p> <p><img src="https://simg.open-open.com/show/95957d64593f9f1c73d7d8bf56e27a1b.jpg"></p> <p>8. 每条信息都有一张随机的猫的图片。</p> <p><img src="https://simg.open-open.com/show/9e0d92e7364394a151f312f4e5405ea4.jpg"></p> <p>9. 我们不应该将信息分批给产品经理,而是应该将这些信息打包成一个列表。</p> <p><img src="https://simg.open-open.com/show/ab70419e118cee5accc6ea2894b4d2a8.jpg"></p> <p>10. 现在产品经理可以在屏幕上显示结果了。</p> <p><img src="https://simg.open-open.com/show/86c30cb8aa5f24e8a3aa9cd4cc7174ca.jpg"></p> <p>这就是我们要做的。我们已经通过流水线上的工人完整地实现了全部功能并最后将结果显示到了屏幕上。</p> <h3>若用 RxJava 实现这些需求该怎么做呢?</h3> <p>RxJava 中存在很多可观察对象(它传出的数据可以被我们观察到)和观察者(它观察并处理可观察对象传出的数据)。在我们的例子里流水线就是可观察对象而产品经理就是观察者。需要注意的是观察者启动整个流水线这个步骤是十分重要的。如果没有观察者,流水线是不会启动的。</p> <p><img src="https://simg.open-open.com/show/b8ad672dbdeae4f958bcadf6b0836f70.jpg"></p> <p>那么流水线上的工人算什么呢?在 RxJava 的世界里它们叫做操作符。它们的动作十分像工人 - 他们需要处理那些被传出的数据(例如仅仅让唯一的数据通过)</p> <p><img src="https://simg.open-open.com/show/d469ccd5233e7b0e8b6d4058f2757123.jpg"></p> <h3>在代码世界实现流水线</h3> <p>很好。可这么多图片有卵用?代码终究还是代码并不是一条流水线,不是吗?</p> <pre> <code class="language-java">mCatFactsService.getCatFacts(100) .map(catFactResponse -> catFactResponse.getFacts()) .flatMap(catFacts -> Observable.from(catFacts)) .distinct() .filter(catFact -> catFact.length() <= 300) .take(3) .map(catFact -> new CatFactWithImage(catFact, getRandomCatImageId())) .toList() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(mCatFactsAdapter::setCatFactWithImages, Throwable::printStackTrace);</code></pre> <p>这样就能通过 RxJava 实现流水线了。为了理解上面的代码你应该从上到下看一遍。每个动作都是独立运行的 - 一个接着一个。</p> <ul> <li><strong>mCatFactsService.getCatFacts(100)</strong> – 这个就是 <em>GET</em> 方法。在响应里我们获得一个封装了 HTTP 状态码以及一列猫的信息的对象。<code>Observable</code> (即可观察对象)将这个对象(我们叫它 CatFactResponse 吧) 封装起来,现在我们就可以用 RxJava 的操作符对它进行处理。</li> <li><strong>map(), flatMap(), distinct(), filter(), take(), toList()</strong> – 这些就是操作符。它们可以修改被传出的数据 - 一个接着一个。它们就像上面例子的图片中流水线上的工人。</li> <li><strong>subscribe()</strong> – 让一个 <code>Observer</code> (即观察者)去订阅一个 <code>Observable</code>。若没有这一步整个流水线都无法启动。</li> <li><strong>subscribeOn()</strong> – 告诉 <code>Observable</code> 当 <em>subscribe()</em> 方法被调用后它应该在哪个线程被启动。接着每个操作符都会在那个线程工作直到我们改变线程。</li> <li><strong>observeOn()</strong> – 改变下一个操作符的工作线程。每个在这个方法之后的操作符都会在它指定的新的线程里工作,直到我们通过别的 <em>observeOn()</em> 改变线程。</li> </ul> <p>现在我们再仔细分析我们用过的每个操作符</p> <pre> <code class="language-java">.map(new Func1<CatFactResponse, List<String>>() { @Override public List<String> call(CatFactResponse catFactResponse) { return catFactResponse.getFacts(); } })</code></pre> <p>有可能你会不适应那花俏的箭头所以我将完整的实现展开了(当然我们可以通过 Java8 的 lambda 表达式实现那种语法 - 详情见<a href="/misc/goto?guid=4958870212861858623"> retrolambda </a>)</p> <p>正如你说看到的, <em>mCatFactsService.getCatFacts()</em> 返回一串数据并被传到 <em>CatFactResponse</em> 中,但因为现在我们不需要 HTTP 状态码,所以我们通过 <strong>MAP</strong> (转换)操作符将 CatFactResponse 对象转换成别的对象 - 在这个例子中是 List <string> 对象。下一个操作符将会处理这个对象。</p> <pre> <code class="language-java">.flatMap(new Func1<List<String>, Observable<? extends String>>() { @Override public Observable<? extends String> call(List<String> catFacts) { return Observable.from(catFacts); } })</code></pre> <p><code>flatMap</code> 操作符接受猫的信息列表作为参数,分别取出列表的每条数据并抛给下一个操作符。所以我们拿到并抛出的数据都是 <strong>FROM</strong> (来自)这个列表的。</p> <pre> <code class="language-java">.distinct()</code></pre> <p>这个操作符用来处理重复项,并且它不会让任何已经通过的相同字符串再次通过。每个都是 <strong>DISTINCT</strong> (独特的)。</p> <pre> <code class="language-java">java .filter(new Func1<String, Boolean>() { @Override public Boolean call(String catFact) { return catFact.length() <= 300; } })</code></pre> <p><code>filter</code> 操作符就是个简单的对/错判断表达式。如果字符串太长,将无法通过。所以 <code>filter</code> 很显然是用来 <strong>FILTERS</strong> (过滤的)。</p> <pre> <code class="language-java">.take(3)</code></pre> <p><code>take</code> 操作符 <strong>TAKES</strong> (取出)指定数量的信息。</p> <pre> <code class="language-java">.map(new Func1<String, CatFactWithImage>() { @Override public CatFactWithImage call(String catFact) { return new CatFactWithImage(catFact, getRandomCatImageId()); } })</code></pre> <p>另一个 <code>map</code> 操作符。在我们打包所有字符串之前我们应该为每个字符串添加张猫的图片。</p> <pre> <code class="language-java">.toList()</code></pre> <p>现在我们可以打包所有 <em>CatFactWithImage</em> 对象 <strong>TO</strong> (成)一个 <strong>LIST</strong> (列表)了。</p> <pre> <code class="language-java">.subscribe(new Observer<List<CatFactWithImage>>() { @Override public void onCompleted() { //no-op } @Override public void onError(Throwable e) { e.printStackTrace(); } @Override public void onNext(List<CatFactWithImage> catFactWithImages) { mCatFactsAdapter.setCatFactWithImages(catFactWithImages); } });</code></pre> <p>然后就是简单地将 <code>list</code> 对象传给 <code>adapter</code> 对象而已。</p> <h2>结论</h2> <p>RxJava 是款十分强大的工具。但不幸的是如果你之前没有通过“流”的形式写过代码你可能很难理解它并学会如何去用它。因为它十分不同于以往平常的安卓开发,所以我们需要一些比代码更形象的东西去理解它。我希望这篇文章能帮助你更好地理解 RxJava 是如何工作的。</p> <p><a href="/misc/goto?guid=4959672017148026329">稀土</a>(简书作者)<br> </p>