用工廠流水線的方式來理解 RxJava 的概念
為什么另寫一篇 RxJava 的文章?
已經有很多 RxJava 的文章通過例子闡述了什么是 RxJava 以及怎么去用,但它們大多數只有代碼。雖然也會通過類比來解釋,例如最出名的就是「流」。通常情況下代碼能完美地讓人理解(我們都是程序員,對吧?),但是 RxJava 十分不同于以往的 Android 開發。在最開始時通過代碼是很難讓人理解的,用「流」來類比并不足夠,即使是 marbles 的例子也還遠遠不夠。我可以保證自己能理解,但對于別人,老實說,難道你們不需要更多結合實際的例子?難道你們不想在腦海中舉一個例子來讓自己更好地理解 RxJava 嗎?我做了,并且我想和你們分享。
工廠流水線
好吧,我說謊了。為了理解 RxJava,我在腦海里舉了不僅僅一個例子。例如我嘗試觀察動物園籠子的動物,嘗試觀察河流里的魚,也嘗試去觀察蝙蝠俠里的犯罪(額,這不是現實生活中的,但不失為一個很好的例子)。但我還是認為工廠流水線是最好的例子。
需求
我們先想象下,我們需要寫一款應用來展示動物們的信息,并且現在我需要在新的界面實現以下功能:
- 三份關于貓的信息
- 每份都是唯一的
- 每份都應該少于 300 字符
- 每份都配張貓的圖片
啟動流水線!
我們嘗試通過流水線上的工人們的幫助實現那些功能。
1. 首先我們需要啟動產品處理進程。僅僅有流水線是不足夠的 - 還需要有人去啟動它。例如一個對結果感興趣的產品經理
2. 現在我們需要隨機獲得有關貓的信息。但怎么做呢?幸運的是這里有 API 能讓我們很容易做到!真的是巧合么?好吧,我們先通過 GET 方法獲取這些信息。
3. 現在我們需要處理來自 API 的響應。它是由 HTTP 狀態碼和一列有關貓的信息組成。我們并不需要狀態碼所以我們首先去除它并將信息列表傳給下一個工人。
4. 下一件要做的事就是將信息列表的信息一個個抽離出來。為什么要這么做?因為這樣做方便下游的工人操作單個字符串(例如檢查字符串是否過長)。
5. 每條信息都是唯一的。下一個工人的任務是清除重復項。
6. 每條信息都不能太長(少于 300 字符)
7. 現在我們的信息是唯一的且長度也符合要求可數量太多了而我們僅僅需要 3 份。所以下一個工人應該清除多余的信息。
8. 每條信息都有一張隨機的貓的圖片。
9. 我們不應該將信息分批給產品經理,而是應該將這些信息打包成一個列表。
10. 現在產品經理可以在屏幕上顯示結果了。
這就是我們要做的。我們已經通過流水線上的工人完整地實現了全部功能并最后將結果顯示到了屏幕上。
若用 RxJava 實現這些需求該怎么做呢?
RxJava 中存在很多可觀察對象(它傳出的數據可以被我們觀察到)和觀察者(它觀察并處理可觀察對象傳出的數據)。在我們的例子里流水線就是可觀察對象而產品經理就是觀察者。需要注意的是觀察者啟動整個流水線這個步驟是十分重要的。如果沒有觀察者,流水線是不會啟動的。
那么流水線上的工人算什么呢?在 RxJava 的世界里它們叫做操作符。它們的動作十分像工人 - 他們需要處理那些被傳出的數據(例如僅僅讓唯一的數據通過)
在代碼世界實現流水線
很好。可這么多圖片有卵用?代碼終究還是代碼并不是一條流水線,不是嗎?
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);
這樣就能通過 RxJava 實現流水線了。為了理解上面的代碼你應該從上到下看一遍。每個動作都是獨立運行的 - 一個接著一個。
- mCatFactsService.getCatFacts(100) – 這個就是 GET 方法。在響應里我們獲得一個封裝了 HTTP 狀態碼以及一列貓的信息的對象。
Observable
(即可觀察對象)將這個對象(我們叫它 CatFactResponse 吧) 封裝起來,現在我們就可以用 RxJava 的操作符對它進行處理。 - map(), flatMap(), distinct(), filter(), take(), toList() – 這些就是操作符。它們可以修改被傳出的數據 - 一個接著一個。它們就像上面例子的圖片中流水線上的工人。
- subscribe() – 讓一個
Observer
(即觀察者)去訂閱一個Observable
。若沒有這一步整個流水線都無法啟動。 - subscribeOn() – 告訴
Observable
當 subscribe() 方法被調用后它應該在哪個線程被啟動。接著每個操作符都會在那個線程工作直到我們改變線程。 - observeOn() – 改變下一個操作符的工作線程。每個在這個方法之后的操作符都會在它指定的新的線程里工作,直到我們通過別的 observeOn() 改變線程。
現在我們再仔細分析我們用過的每個操作符
.map(new Func1<CatFactResponse, List<String>>() {
@Override
public List<String> call(CatFactResponse catFactResponse) {
return catFactResponse.getFacts();
}
})
有可能你會不適應那花俏的箭頭所以我將完整的實現展開了(當然我們可以通過 Java8 的 lambda 表達式實現那種語法 - 詳情見 retrolambda )
正如你說看到的, mCatFactsService.getCatFacts() 返回一串數據并被傳到 CatFactResponse 中,但因為現在我們不需要 HTTP 狀態碼,所以我們通過 MAP (轉換)操作符將 CatFactResponse 對象轉換成別的對象 - 在這個例子中是 List <string> 對象。下一個操作符將會處理這個對象。
.flatMap(new Func1<List<String>, Observable<? extends String>>() {
@Override
public Observable<? extends String> call(List<String> catFacts) {
return Observable.from(catFacts);
}
})
flatMap
操作符接受貓的信息列表作為參數,分別取出列表的每條數據并拋給下一個操作符。所以我們拿到并拋出的數據都是 FROM (來自)這個列表的。
.distinct()
這個操作符用來處理重復項,并且它不會讓任何已經通過的相同字符串再次通過。每個都是 DISTINCT (獨特的)。
java
.filter(new Func1<String, Boolean>() {
@Override
public Boolean call(String catFact) {
return catFact.length() <= 300;
}
})
filter
操作符就是個簡單的對/錯判斷表達式。如果字符串太長,將無法通過。所以 filter
很顯然是用來 FILTERS (過濾的)。
.take(3)
take
操作符 TAKES (取出)指定數量的信息。
.map(new Func1<String, CatFactWithImage>() {
@Override
public CatFactWithImage call(String catFact) {
return new CatFactWithImage(catFact, getRandomCatImageId());
}
})
另一個 map
操作符。在我們打包所有字符串之前我們應該為每個字符串添加張貓的圖片。
.toList()
現在我們可以打包所有 CatFactWithImage 對象 TO (成)一個 LIST (列表)了。
.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>
然后就是簡單地將 list
對象傳給 adapter
對象而已。
結論
RxJava 是款十分強大的工具。但不幸的是如果你之前沒有通過“流”的形式寫過代碼你可能很難理解它并學會如何去用它。因為它十分不同于以往平常的安卓開發,所以我們需要一些比代碼更形象的東西去理解它。我希望這篇文章能幫助你更好地理解 RxJava 是如何工作的。
稀土(簡書作者)