本文是 "RxJava 沉思錄" 系列的第二篇分享。本系列所有分享:
在上一篇分享中,我們澄清了目前有關 RxJava 的幾個最流行的誤解,它們分別是:“鏈式編程是 RxJava 的厲害之處”,“RxJava 等于異步加簡潔”,“RxJava 是用來解決 Callback Hell 的”。在上一篇的最后,我們了解了 RxJava 其實給我們最基礎的功能就是幫我們統(tǒng)一了所有異步回調(diào)的接口。但是 RxJava 并不止于此,本文我們將首先介紹 Observable 在空間維度上重新組織事件的能力。
從一個簡單的例子說起
情景:有一個相冊應用,從網(wǎng)絡獲取當前用戶的照片列表,展示在 RecyclerView 里:
public interface NetworkApi {
@GET("/path/to/api")
Call<List<Photo>> getAllPhotos();
}
上面是使用 Retrofit 定義的從網(wǎng)絡獲取照片的 API 的接口。大家都知道,如果我們使用 Retrofit 的 RxJavaCallAdapter 就可以把接口中的返回類型從 Call<List<Photo>> 轉(zhuǎn)為 Observable<List<Photo>>:
public interface NetworkApi {
@GET("/path/to/api")
Observable<List<Photo>> getAllPhotos();
}
那么我們使用這個接口展示照片的代碼應該長下面這樣:
NetworkApi networkApi = ...
networkApi.getAllPhotos()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(photos -> {
adapter.setData(photos);
adapter.notifyDataSetChanged();
});
現(xiàn)在新加一個需求,請求當前用戶照片列表這個網(wǎng)絡請求,需要加入緩存功能(緩存的是網(wǎng)絡響應中的圖片的URL,圖片的 Bitmap 緩存交給專門的圖片加載框架,例如 Glide),也就是說,當用戶希望展示圖片列表時,先去緩存讀取用戶的照片列表進行加載(如果緩存里有這個接口的上次訪問的數(shù)據(jù)),同時發(fā)起網(wǎng)絡請求,待網(wǎng)絡請求返回之后,更新緩存,同時使用使用最新的返回數(shù)據(jù)刷新照片列表。如果我們選擇使用 JakeWharton 的 DiskLruCache 作為我們的緩存介質(zhì),那么上面的代碼將變?yōu)椋?/p>
DiskLruCache cache = ...
DiskLruCache.Snapshot snapshot = cache.get("getAllPhotos");
if (snapshot != null) {
// 讀取緩存數(shù)據(jù)并反序列化
List<Photo> cachedPhotos = new Gson().fromJson(
snapshot.getString(VALUE_INDEX),
new TypeToken<List<Photo>>(){}.getType()
);
// 刷新照片列表
adapter.setData(photos);
adapter.notifyDataSetChanged();
}
NetworkApi networkApi = ...
networkApi.getAllPhotos()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(photos -> {
adapter.setData(photos);
adapter.notifyDataSetChanged();
// 更新緩存
DiskLruCache.Editor editor = cache.edit("getAllPhotos");
editor.set(VALUE_INDEX, new Gson().toJson(photos)).commit();
});
上面的代碼就是最直觀的可以解決需求的代碼,我們進一步思考一下,讀取文件緩存也屬于耗時操作,我們最好把它封裝為異步任務,既然網(wǎng)絡請求已經(jīng)被封裝成 Observable 了,我們嘗試把讀取文件緩存也封裝為 Observable :
Observable<List<Photo>> cachedObservable = Observable.create(emitter -> {
DiskLruCache.Snapshot snapshot = cache.get("getAllPhotos");
if (snapshot != null) {
List<Photo> cachedPhotos = new Gson().fromJson(
snapshot.getString(VALUE_INDEX),
new TypeToken<List<Photo>>(){}.getType()
);
emitter.onNext(cachedPhotos);
}
emitter.onComplete();
});
到目前為止,發(fā)起網(wǎng)絡請求和讀取緩存這兩個異步操作都被我們封裝成了 Observable 的形式,前面做了這么多鋪墊,接下來進入正題:把原先的面向 Callback 的異步操作統(tǒng)一改寫為 Observable 的形式以后,首先帶來的好處就是可以對 Observable 在空間維度上進行重新組織。
networkApi.getAllPhotos()
.doOnNext(photos ->
// 更新緩存
cache.edit("getAllPhotos")
.set(VALUE_INDEX, new Gson().toJson(photos))
.commit()
)
// 讀取現(xiàn)有緩存
.startWith(cachedObservable)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(photos -> {
adapter.setData(photos);
adapter.notifyDataSetChanged();
});
調(diào)用
startWith操作符后,會生成一個新的 Observable,新的Observable會首先發(fā)射傳入的Observable包含的元素,而后才會發(fā)射原來的Observable包含的元素。例如ObservableA 包含 a1, a2 兩個元素,ObservableB 包含 b1, b2 兩個元素,那么 b.startWith(a) 返回的新Observable發(fā)射序列順序為: a1, a2, b1, b2?!?參考資料:StartWith
在上面的例子中,我們連接了網(wǎng)絡請求和讀取緩存這兩個 Observable,原先需要分別處理結果的兩個異步任務,我們現(xiàn)在把它們結合成了一個,指定了一個觀察者就滿足了需求。這個觀察者會被回調(diào) 2 次,第一次是來自緩存的結果,第二次是來自網(wǎng)絡的結果,體現(xiàn)在界面上就是列表刷新了兩次。
這里引發(fā)了我們的思考,原先 Callback 的寫法,如果我們有 n 個異步任務,我們就需要指定 n 個回調(diào);而如果在 n 個異步任務都已經(jīng)被封裝成 Observable 的情況下,我們就可以對 Observable 進行分類、組合、變換,經(jīng)過這樣的處理以后,我們的觀察者的數(shù)量就會減少,而且職責會變的簡單而直接,只需要對它所關心的數(shù)據(jù)類型做出響應,而不需要關心數(shù)據(jù)從何而來,經(jīng)歷過怎樣的變化。
我們再進一步,上面的例子再加一個需求:如果從網(wǎng)絡請求回來的數(shù)據(jù)和緩存中提前響應的數(shù)據(jù)一致,就不需要再刷新一次了。也就是說,如果緩存數(shù)據(jù)和網(wǎng)絡數(shù)據(jù)一致,那緩存數(shù)據(jù)刷新一次列表以后,網(wǎng)絡數(shù)據(jù)不需要再去刷新一次列表了。
我們考慮一下,如果我們使用傳統(tǒng) Callback 的形式,指定了兩個 Callback 去處理這個需求,為了保證第二次網(wǎng)絡請求回來的相同數(shù)據(jù)不刷新,我們勢必需要在兩個 Callback 之外,定義一個變量來保存緩存數(shù)據(jù),然后在網(wǎng)絡請求的回調(diào)內(nèi),比較兩個值,來決定是否需要刷新界面。
但如果我們用 RxJava 如何來實現(xiàn)這個需求,該如何寫呢:
networkApi.getAllPhotos()
.doOnNext(photos ->
cache.edit("getAllPhotos")
.set(VALUE_INDEX, new Gson().toJson(photos))
.commit()
)
.startWith(cachedObservable)
// 保證不會出現(xiàn)相同數(shù)據(jù)
.distinctUntilChanged()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(photos -> {
adapter.setData(photos);
adapter.notifyDataSetChanged();
});
distinctUntilChanged操作符用來確保Observable發(fā)射的元素里,相鄰的兩個元素必須是不相等的。 參考資料:Distinct
與原先的寫法相比,只多了一行 .distinctUntilChanged() ( 我們假設用于比較兩個對象是否相等的 equals 方法已經(jīng)實現(xiàn) ),就可以滿足,在網(wǎng)絡數(shù)據(jù)和緩存數(shù)據(jù)一致的情況下,觀察者只回調(diào)一次。
我們比較一下使用 Callback 的寫法和使用 Observable 進行組裝的寫法,可以發(fā)現(xiàn),使用 Callback 的寫法,經(jīng)常會由于需求的變化,導致 Callback 內(nèi)部的邏輯發(fā)生變動,而使用 Observable 的寫法,觀察者的核心邏輯則較為穩(wěn)定,很少發(fā)生變化(本例中為刷新列表)。Observable 通過內(nèi)置的操作符對自身發(fā)射的元素在空間維度上重新組織,或者與其他的 Observable 一起在空間維度上進行重新組織,使得觀察者的邏輯簡單而直接,不需要關心數(shù)據(jù)從何而來,從而使觀察者的邏輯較為穩(wěn)定。
一個復雜的例子
情景:實現(xiàn)一個具有多種類型的 RecyclerView,如圖所示:

假設列表中有 3 種類型的數(shù)據(jù),這 3 種類型共同填充了一個 RecyclerView,簡單起見,我們定義 Retrofit 接口如下:
public interface NetworkApi {
@GET("/path/to/api")
Observable<List<ItemA>> getItemListOfTypeA();
@GET("/path/to/api")
Observable<List<ItemB>> getItemListOfTypeB();
@GET("/path/to/api")
Observable<List<ItemC>> getItemListOfTypeC();
}
到目前為止,情況還是簡單的, 我只要維護 3 個 RecyclerView 并分別各自更新即可。但是我們現(xiàn)在接到新加需求,這 3 種類型的數(shù)據(jù)在列表中出現(xiàn)的順序是可配置的,而且 3 種類型數(shù)據(jù)不一定全部需要展示,也就是說可能展示 3 種,也可能只展示其中 2 種。我們定義與之對應的接口:
public interface NetworkApi {
@GET("/path/to/api")
Observable<List<ItemA>> getItemListOfTypeA();
@GET("/path/to/api")
Observable<List<ItemB>> getItemListOfTypeB();
@GET("/path/to/api")
Observable<List<ItemC>> getItemListOfTypeC();
// 需要展示的數(shù)據(jù)順序
@GET("/path/to/api")
Observable<List<String>> getColumns();
}
新加的 getColumns 接口,返回的數(shù)據(jù)形如:
["a", "b", "c"]["b", "a"]["b", "c"]
首先考慮使用普通的 Callback 形式如何來實現(xiàn)這個需求。由于 3 種數(shù)據(jù)現(xiàn)在順序可變,數(shù)量也無法確定,如果還是考慮由多個 RecyclerView 來維護的話需要在布局中調(diào)用 addView, removeView
來添加移除 RecyclerView,這樣的話性能上不夠好,我們考慮把所有數(shù)據(jù)填充到一個 RecyclerView 中,不同類型的數(shù)據(jù)通過不同 ItemType 進行區(qū)分。下面的代碼中我依然使用了 Observable ,只是我僅僅把它當成普通的 Callback 功能使用:
private NetworkApi networkApi = ...
// 不同類型數(shù)據(jù)出現(xiàn)的順序
private List<String> resultTypes;
// 這些類型對應的數(shù)據(jù)的集合
private LinkedList<List<? extends Item>> responseList;
public void refresh() {
networkApi.getColumns().subscribe(columns -> {
// 保存配置的欄目順序
resultTypes = columns;
responseList = new LinkedList<>(Collections.nCopies(columns.size(), new ArrayList<>()));
for (String type : columns) {
switch (type) {
case "a":
networkApi.getItemListOfTypeA().subscribe(data -> onOk("a", data));
break;
case "b":
networkApi.getItemListOfTypeB().subscribe(data -> onOk("b", data));
break;
case "c":
networkApi.getItemListOfTypeC().subscribe(data -> onOk("c", data));
break;
}
}
});
}
private void onOk(String type, List<? extends Item> response) {
// 按配置的順序,更新對應位置上的數(shù)據(jù)
responseList.set(resultTypes.indexOf(type), response);
// 把當前已返回的數(shù)據(jù)填充到一個 List 中
List<Item> data = new ArrayList<>();
for (List<? extends Item> itemList: responseList) {
data.addAll(itemList);
}
// 更新列表
adapter.setData(data);
adapter.notifyDataSetChanged();
}
上面的代碼,為了避免 Callback Hell 出現(xiàn),我已經(jīng)提前把 onOk 提到了外部層次,使代碼便于從上往下閱讀。但是不知道你有沒有和我相同的感覺,就是類似這樣的代碼總給人一種不是很 “內(nèi)聚” 的感覺,就是為了把 Callback 展平,導致一些中間變量被暴露到了外層空間。
帶著這個問題,我們先分析一下數(shù)據(jù)流動:
-
refresh方法發(fā)起第一次請求,得到需要被展示的 n 種數(shù)據(jù)的類型以及順序。 - 根據(jù)第一次請求的結果,發(fā)起 n 次請求,分別得到每種數(shù)據(jù)的結果。
-
onOk方法作為觀察者, 會被回調(diào) n 次,按照第一個接口里返回的順序正確的匯總 2 中每個數(shù)據(jù)接口返回的結果,并且通知界面更新。
有點像寫作文一樣,這是一種 總——分——總 的結構。
Observable 在空間維度重新組織事件
接下來我們使用 RxJava 來實現(xiàn)這個需求,我們會用到 RxJava 的一些操作符,來對 Observable 進行重新組織:
NetworkApi networkApi = ...
networkApi.getColumns()
.map(types -> {
List<Observable<? extends List<? extends Item>>> requestObservableList = new ArrayList<>();
for (String type : types) {
switch (type) {
case "a":
requestObservableList.add(
networkApi.getItemListOfTypeA().startWith(new ArrayList<ItemA>())
);
break;
case "b":
requestObservableList.add(
networkApi.getItemListOfTypeB().startWith(new ArrayList<ItemB>())
);
break;
case "c":
requestObservableList.add(
networkApi.getItemListOfTypeC().startWith(new ArrayList<ItemC>())
);
break;
}
}
return requestObservableList;
})
.flatMap(requestObservables -> Observable.combineLatest(requestObservables, objects -> {
List<Item> items = new ArrayList<>();
for (Object response : objects) {
items.addAll((List<? extends Item>) response);
}
return items;
}))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(data -> {
adapter.setData(data);
adapter.notifyDataSetChanged();
});
我們一步一步分析 RxJava 處理的具體步驟。首先是第一步,獲取需要展示的欄目列表,這是最簡單的,networkApi.getColumns() 這個方法返回是一個只發(fā)射一個元素的 Observable,這個元素即為展示的欄目列表,為了方便后續(xù)討論,假設欄目的順序為 ["a", "b", "c"], 如下圖所示:

接下來的操作符是 map 操作符,原來的 Observable 進行了變換,變成了一個新的 Observable,新的 Observable 還是只發(fā)射一個元素,這個元素的類型還是 List ,只不過 List 內(nèi)部的數(shù)據(jù)類型從原先的字符串(代表數(shù)據(jù)類型)變成了 Observable。Observable 發(fā)射的元素還可以是 “Observable 的 List ” 嗎?是的,沒有什么不可以 : )

map操作符負責把一個Observable里發(fā)射的元素全部進行轉(zhuǎn)換,生成一個發(fā)射新的元素的Observable,元素的種類會發(fā)生改變,但是發(fā)射的元素的數(shù)量不會發(fā)生改變。 參考資料:Map
這個操作,在業(yè)務上的含義是,根據(jù)上一步取回的欄目列表,即 ["a", "b", "c"],根據(jù)不同的數(shù)據(jù)類型,分別發(fā)起請求去獲取對應欄目的數(shù)據(jù)列表,例如欄目類型是 a 的話,就對應發(fā)起 networkApi.getItemListOfTypeA() 請求。這里有一點值得注意,就是每一個具體的請求后面都跟了一個 .startWith(new ArrayList<>()),也就是說每個具體請求欄目內(nèi)容的 Observable 在返回真正的數(shù)據(jù) List 之前都會返回一個空的 List ,這里這么處理的原因我們會在下一步中解釋。
接下來這一步可能是最難理解的一步了,map 操作之后,緊接著是 flatMap 操作符,而 flatMap 操作符傳入的 lambda 表達式內(nèi)部,又調(diào)用了 Observable.combineLatest 操作符,我們先從里面的 combineLatest 操作符開始講起,請看下圖:

combineLatest 操作符的第一個參數(shù) requestObservables,它的類型是 Observable 的 List,它就是上一步中 map 操作符進行變換之后,新的 Observable 發(fā)射的數(shù)據(jù),即由
networkApi.getItemListOfTypeA().startWith(...)networkApi.getItemListOfTypeB().startWith(...)networkApi.getItemListOfTypeC().startWith(...)
3 個 Observable 組成的 List。
combineLatest 操作符的第二個參數(shù)是個 lambda 表達式,這個 lambda 表達式的參數(shù)類型是 Object[],這個數(shù)組的長度等于 requestObservables 的長度,Object[] 數(shù)組中每個元素即為 requestObservables 中每個 Observable 發(fā)射的元素,即:
-
Object[0]對應requestObservables[0]發(fā)射的元素 -
Object[1]對應requestObservables[1]發(fā)射的元素 -
Object[2]對應requestObservables[2]發(fā)射的元素
那這個 lambda 表達式被調(diào)用的時機是什么時候呢?當 requestObservables 中任意一個 Observable 發(fā)射一個元素時,這個元素便會和 requestObservables 中剩余的所有 Observable 最近一次 發(fā)射的元素一起,作為參數(shù)調(diào)用這個 lambda 表達式。
那么整個 combineLatest 操作符的作用就是,返回一個新的 Observable, 根據(jù)第一個參數(shù)里輸入的一組 Obsevable,按照上面說的時機,調(diào)用第二個參數(shù)里的那個 lambda 表達式,把這個 lambda 表達式的返回值,作為新的 Observable 發(fā)射的值,lambda 被調(diào)用幾次,就發(fā)射幾個元素。
我們這里 lambda 表達式內(nèi)部的邏輯比較簡單,就是把 3 個接口里返回的數(shù)據(jù)進行匯總,組成一個新的 List 。我們再回過頭看上面那張圖,我們可以看到,Observable.combinLatest 返回的新的 Observable 一共發(fā)射了 4 個元素,它們分別是:
[][{ItemB}, {ItemB}, ...][{ItemA}, {ItemA}, ..., {ItemB}, {ItemB}, ...][{ItemA}, {ItemA}, ..., {ItemB}, {ItemB}, ..., {ItemC}, {ItemC}, ...]
前面留了一個問題沒有解釋,為什么 3 個獲取具體的欄目數(shù)據(jù)的接口需要調(diào)用 startWith 操作符發(fā)射一個空白列表,就像這樣:networkApi.getItemListOfTypeA().startWith(...),現(xiàn)在這個答案應該清晰了,如果不調(diào)用這個操作符,那么 combineLatest 操作符生成的新 Observable 將會只發(fā)射一個元素, 即上面 4 個元素的最后一個,從用戶的感受來看,必須要等所有欄目全部請求成功以后才會一次性展示,而不是漸進地展示。
說完了內(nèi)部的 combineLatest 操作符,現(xiàn)在該說外層的 flatMap 操作符了,flatMap 操作符也會生成一個新的 Observable,它會通過傳入的 lambda 表達式,把舊的 Observable 里發(fā)射的每一個元素都映射成一個 Observable,然后把這些 Observable 發(fā)射的所有元素作為新的 Observable 發(fā)射的元素。
由于我們這里的情況,調(diào)用 flatMap 之前的 Observable 只發(fā)射了一個元素,所以 flatMap 之后生成的新 Observable 發(fā)射的元素,就是 flatMap 操作符傳入的那個 lambda 表達式執(zhí)行完生成的那個 Observable 所發(fā)射的元素,也就是說 flatMap 操作符執(zhí)行完后的那個新的 Observable 發(fā)射的元素,和我們剛剛討論的 combineLatest 操作符執(zhí)行完后的 Observable 發(fā)射的元素是一致的。
到這里為止,RxJava 實現(xiàn)的版本的每一步我們都解釋完了,我們回過頭重新梳理一下 RxJava 對 Observable 進行變換的過程,如下圖:

通過 RxJava 的操作符,我們把 networkApi 里的 4 個接口返回的 4 個 Observable,在空間維度進行了重新組織,最終把它們轉(zhuǎn)成了一個 Observable,這個 Observable 發(fā)射的元素類型是 List<Item>,而這正是我們的觀察者 -- Adapter 所關心的數(shù)據(jù)類型,觀察者只需要監(jiān)聽這個 Observable ,并更新數(shù)據(jù)即可。
我們在講 RxJava 實現(xiàn)的這個版本之前的時候,說到過 Callback 實現(xiàn)的版本不夠 內(nèi)聚,比較一下現(xiàn)在這個 RxJava 的版本,確實可以發(fā)現(xiàn)的確 RxJava 這個版本更內(nèi)聚。但是并非 Callback 版本沒有辦法做到更內(nèi)聚,我們可以把 Callback 版本里的 onOk, refresh,resultTypes, responseList 這幾個方法和字段封裝到一個對象中,對外只暴露 refresh 方法和一個設置觀察者的方法,也可以做到一樣的內(nèi)聚,但是這就需要額外的工作量了??扇绻覀兪褂?RxJava 就不一樣了,它提供了一堆現(xiàn)成的操作符,通過 Observable 之間的變換與重組,直接就可以寫出內(nèi)聚的代碼。
在上面代碼里出現(xiàn)的所有操作符中,最核心的一個操作符就是 combineLatest 操作符,仔細比較 RxJava 版本和 Callback 版本就可以發(fā)現(xiàn),combineLatest 操作符的功能其實和 Callback 版本里的 onOk 方法前半部分, resultTypes, responseList 合在一起功能是相當?shù)?,一方面負責收集多個接口返回的數(shù)據(jù),另一方面保證收集回來的數(shù)據(jù)的順序是和上一個接口返回的應該展示的數(shù)據(jù)的順序是一致的。
一種更加函數(shù)式的寫法
從代碼量上來看,RxJava 版本與 Callback 版本相差無幾,對函數(shù)式編程比較擅長的人來說,RxJava 版本里 for 循環(huán)的寫法,不夠 “函數(shù)式”,我們可以把原來的寫法改成一種更緊湊、更函數(shù)式的寫法:
NetworkApi networkApi = ...
netWorkApi.getColumns()
.flatMap(types -> Observable.fromIterable(types)
.map(type -> {
switch (type) {
case "a": return netWorkApi.getItemListOfTypeA().startWith(new ArrayList<ItemA>());
case "b": return netWorkApi.getItemListOfTypeB().startWith(new ArrayList<ItemB>());
case "c": return netWorkApi.getItemListOfTypeC().startWith(new ArrayList<ItemC>());
default: throw new IllegalArgumentException();
}
})
.<List<Observable<? extends List<? extends Item>>>>collectInto(new ArrayList<>(), List::add)
.toObservable()
)
.flatMap(requestObservables -> Observable.combineLates(requestObservables, objects -> objects))
.flatMap(objects -> Observable.fromArray(objects)
.<List<Item>>collectInto(new ArrayList<>(), (items, o) -> items.addAll((List<Item>) o))
.toObservable()
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(data -> {
adapter.setData(data);
adapter.notifyDataSetChanged();
});
這里引入了一個新的操作符
collectInto,用于把一個Observable里面發(fā)射的元素,收集到一個可變的容器內(nèi)部,本例中用它來替換for循環(huán)相關邏輯,具體內(nèi)容這里不再詳細展開。
參考資料:CollectInto
小結
第二個例子花了這么大篇幅來講,超出了我一開始的預期,這也可以看出來的確 RxJava 學習的曲線是陡峭的,不過我認為這個例子很好的表達我這一小節(jié)要闡述的觀點,即 Observable 在空間維度上對事件的重新組織,讓我們的事件驅(qū)動型編程更具想象力 ,因為原先的編程中,我們面對多少個異步任務,就會寫多少個回調(diào),如果任務之間有依賴關系,我們的做法就是修改觀察者(回調(diào)函數(shù))邏輯以及新增數(shù)據(jù)結構保證依賴關系,RxJava 給我們帶來的新思路是,Observable 的事件在到達觀察者之前,可以先通過操作符進行一系列變換(當然變換的規(guī)則還是和具體業(yè)務邏輯有關的),對觀察者屏蔽數(shù)據(jù)產(chǎn)生的復雜性,只提供給觀察者簡單的數(shù)據(jù)接口。
那么是否在這個例子中,是否 RxJava 的版本更好呢,我個人的觀點是雖然 RxJava 版本展現(xiàn)了其更有想象力的編程方式,但是就這個具體的例子,兩者并沒有太大的差距。RxJava 可以寫出更短更內(nèi)聚的代碼,但是編寫和理解的難度較大;Callback 版本雖然樸實無華,但是便于編寫以及理解,可維護性更好。對于兩者的好壞,我們也不要過于著急下結論,不妨繼續(xù)看看 RxJava 還有什么其他的優(yōu)勢。
(未完待續(xù))
本文屬于 "RxJava 沉思錄" 系列,歡迎閱讀本系列的其他分享: