介紹
此文章結合 Github AnalyseRxJava 專案,給 Android 開發者帶來 RxJava 詳細的解說。參考自 RxJava Essential 及書中的例子
關於 RxJava 的由來及簡介,這裡就不在重複了,感興趣的請閱讀 RxJava Essential
。
相關文章連結
- 解剖 RxJava 之過濾操作符
- 解剖 RxJava 之變換操作符
- 解剖 RxJava 之組合操作符(未完待續)
變換操作符
map 家族
RxJava 提供了幾個 mapping 函式:map()
, flatMap()
, concatMap()
, flatMapIterable()
以及 switchMap()
.所有這些函式都作用於一個可觀測序列,然後變換它發射的值,最後用一種新的形式返回它們。
map()
此函式的示意圖為:
RxJava的 map 函式接收一個指定的 Func 物件然後將它應用到每一個由 Observable 發射的值上, 比如獲取 App 列表的方法,就是將原有資料來源的 ResolveInfo
map 為 AppInfo
private void performMap() {
mAppAdapter.clear();
Observable.from(mApps)
.take(3)
.map(new Func1<AppInfo, AppInfo>() {
@Override
public AppInfo call(AppInfo appInfo) {
String name = appInfo.getName();
appInfo.setName(name + " map");
LogUtils.d(TAG, "map -- 1 -- " + appInfo.getName());
return appInfo;
}
})
.subscribe(new Action1<AppInfo>() {
@Override
public void call(AppInfo appInfo) {
mAppAdapter.add(appInfo);
LogUtils.d(TAG, "map -- 2 -- " + appInfo.getName());
}
});
}複製程式碼
map()
被訂閱時每傳遞一個事件執行一次 onNext 方法
map 返回的是結果集
map只能單一轉換,單一隻的是隻能一對一進行轉換,指一個物件可以轉化為另一個物件但是不能轉換成物件陣列(map返回結果集不能直接使用from/just再次進行事件分發,一旦轉換成物件陣列的話,再處理集合/陣列的結果時需要利用for一一遍歷取出,而使用 RxJava 就是為了剔除這樣的巢狀結構,使得整體的邏輯性更強。)
flatMap()
此函式的示意圖為:
在複雜的場景中,我們有一個這樣的 Observable:它發射一個資料序列,這些資料本身也可以發射 Observable。RxJava 的 flatMap()
函式提供一種鋪平序列的方式,然後合併這些 Observables 發射的資料,最後將合併後的結果作為最終的 Observable
當我們在處理可能有大量的 Observables 時,重要是記住任何一個 Observables 發生錯誤的情況,flatMap()
將會觸發它自己的 onError()
函式並放棄整個鏈。
重要的一點提示是關於合併部分:它允許交叉。正如上圖所示,這意味著 flatMap()
不能夠保證在最終生成的 Observable 中源 Observables 確切的發射順序。
private void performFlatMap() {
mAppAdapter.clear();
getApps().take(3)
.flatMap(new Func1<AppInfo, Observable<AppInfo>>() {
@Override
public Observable<AppInfo> call(AppInfo appInfo) {
String name = appInfo.getName();
appInfo.setName(name + " flatMap");
LogUtils.d(TAG, "flatMap -- 1 -- " + appInfo.getName());
return Observable.just(appInfo)
.delay((long) (Math.random() * 2 + 0.5), TimeUnit.SECONDS);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<AppInfo>() {
@Override
public void call(AppInfo appInfo) {
mAppAdapter.add(appInfo);
LogUtils.d(TAG, "flatMap -- 2 -- " + appInfo.getName());
}
});
}複製程式碼
log 結果
8622-8622/com.zhgqthomas.rxjava D/github_TransformObserv: flatMap -- 1 -- Contacts flatMap
8622-8622/com.zhgqthomas.rxjava D/github_TransformObserv: flatMap -- 1 -- Phone flatMap
8622-8622/com.zhgqthomas.rxjava D/github_TransformObserv: flatMap -- 1 -- Settings flatMap
8622-8622/com.zhgqthomas.rxjava D/github_TransformObserv: flatMap -- 2 -- Settings flatMap
8622-8622/com.zhgqthomas.rxjava D/github_TransformObserv: flatMap -- 2 -- Contacts flatMap
8622-8622/com.zhgqthomas.rxjava D/github_TransformObserv: flatMap -- 2 -- Phone flatMap複製程式碼
通過 log 可以發現 flatMap
合併結果是允許交叉的
flatMap
被訂閱時將所有資料傳遞完畢彙總到一個Observable然後一一執行onNext方法(執行順序不同)>>>>(如單純用於一對一轉換則和map相同)
flatmap返回的是包含結果集的Observable(返回結果不同)
flatmap多用於多對多,一對多,再被轉化為多個時,一般利用from/just進行一一分發
flatmap既可以單一轉換也可以一對多/多對多轉換,flatmap要求返回Observable,因此可以再內部進行from/just的再次事件分發,一一取出單一物件(轉換物件的能力不同)
concatMap()
此函式的示意圖為:
private void performConcatMap() {
mAppAdapter.clear();
getApps().take(3)
.concatMap(new Func1<AppInfo, Observable<AppInfo>>() {
@Override
public Observable<AppInfo> call(AppInfo appInfo) {
String name = appInfo.getName();
appInfo.setName(name + " concatMap");
LogUtils.d(TAG, "concatMap -- 1 -- " + appInfo.getName());
return Observable.just(appInfo)
.delay((long) (Math.random() * 2 + 0.5), TimeUnit.SECONDS);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<AppInfo>() {
@Override
public void call(AppInfo appInfo) {
mAppAdapter.add(appInfo);
LogUtils.d(TAG, "concatMap -- 2 -- " + appInfo.getName());
}
});
}複製程式碼
log 資訊
2637-2637/com.zhgqthomas.rxjava D/github_TransformObserv: concatMap -- 1 -- Contacts concatMap
2637-2637/com.zhgqthomas.rxjava D/github_TransformObserv: concatMap -- 1 -- Phone concatMap
2637-2637/com.zhgqthomas.rxjava D/github_TransformObserv: concatMap -- 2 -- Contacts concatMap
2637-3048/com.zhgqthomas.rxjava D/github_TransformObserv: concatMap -- 1 -- Settings concatMap
2637-2637/com.zhgqthomas.rxjava D/github_TransformObserv: concatMap -- 2 -- Phone concatMap
2637-2637/com.zhgqthomas.rxjava D/github_TransformObserv: concatMap -- 2 -- Settings concatMap複製程式碼
通過 log 可以發現 RxJava 的
concatMap()
函式解決了flatMap()
的交叉問題,提供了一種能夠把發射的值連續在一起的鋪平函式,而不是合併它們, 如上述示意圖所示
flatMapIterable()
以下為該函式的示意圖:
作為 map 家族的一員,flatMapInterable()
和 flatMap()
很像。僅有的本質不同是它將源資料生成 Iterable,而不是原始資料項和生成的 Observables。
switchMap()
以下為該函式的示意圖:
switchMap()
和 flatMap()
很像,除了一點:每當源 Observable 發射一個新的資料項(Observable)時,它將取消訂閱並停止監視之前那個資料項產生的 Observable,並開始監視當前發射的這一個。
private void performSwitchMap() {
mAppAdapter.clear();
getApps().take(3)
.switchMap(new Func1<AppInfo, Observable<AppInfo>>() {
@Override
public Observable<AppInfo> call(AppInfo appInfo) {
String name = appInfo.getName();
appInfo.setName(name + " switchMap");
LogUtils.d(TAG, "switchMap -- 1 -- " + appInfo.getName());
return Observable.just(appInfo)
.delay(1, TimeUnit.SECONDS);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<AppInfo>() {
@Override
public void call(AppInfo appInfo) {
mAppAdapter.add(appInfo);
LogUtils.d(TAG, "switchMap -- 2 -- " + appInfo.getName());
}
});
}複製程式碼
log 列印結果
32196-32196/com.zhgqthomas.rxjava D/github_TransformObserv: switchMap -- 1 -- Contacts switchMap
32196-32196/com.zhgqthomas.rxjava D/github_TransformObserv: switchMap -- 1 -- Phone switchMap
32196-32196/com.zhgqthomas.rxjava D/github_TransformObserv: switchMap -- 1 -- Settings switchMap
32196-32196/com.zhgqthomas.rxjava D/github_TransformObserv: switchMap -- 2 -- Settings switchMap複製程式碼
通過 log 可以看出當源 Observable 發射一個新的資料項(Observable)時,它將取消訂閱並停止監視之前那個資料項產生的 Observable,並開始監視當前發射的這一個。
關於switchMap
巧妙應用也可以檢視這篇文章
scan()
以下是該函式的示意圖:
RxJava 的 scan()
函式可以看做是一個累積函式。scan()
函式對原始 Observable 發射的每一項資料都應用一個函式,計算出函式的結果值,並將該值填充回可觀測序列,等待和下一次發射的資料一起使用。
private void performScan() {
mAppAdapter.clear();
getApps().scan(new Func2<AppInfo, AppInfo, AppInfo>() {
@Override
public AppInfo call(AppInfo appInfo, AppInfo appInfo2) {
if (appInfo.getName().length() > appInfo2.getName().length()) {
return appInfo;
} else {
return appInfo2;
}
}
})
.distinct()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<AppInfo>() {
@Override
public void call(AppInfo appInfo) {
mAppAdapter.add(appInfo);
}
});
}複製程式碼
有一個 scan()
函式的變體,它用初始值作為第一個發射的值,方法簽名看起來像:scan(R,Func2)
groupBy()
以下為此函式的示意圖:
通過示意圖可以看出,RxJava 使用 groupBy
從列表中按照指定的規則來分組元素。這個函式將源 Observable 變換成一個發射 Observables 的新的 Observable。它們中的每一個新的 Observable 都發射一組指定的資料。
private void performGroupBy() {
mAppAdapter.clear();
getApps()
.groupBy(new Func1<AppInfo, String>() {
@Override
public String call(AppInfo appInfo) { // 根據第一次安裝時間進行分組
SimpleDateFormat formatter = new SimpleDateFormat("MM/yyyy", Locale.CHINA);
return formatter.format(new Date(appInfo.getFirstInstallTime()));
}
})
.subscribe(new Action1<GroupedObservable<String, AppInfo>>() {
@Override
public void call(final GroupedObservable<String, AppInfo> result) {
// 只顯示 key 為 09/2016 的 Apps
if (!result.getKey().equalsIgnoreCase("09/2016")) {
return;
}
result.toList()
.flatMap(new Func1<List<AppInfo>, Observable<AppInfo>>() {
@Override
public Observable<AppInfo> call(List<AppInfo> appInfos) {
return Observable.from(appInfos);
}
})
.subscribe(new Action1<AppInfo>() {
@Override
public void call(AppInfo appInfo) {
appInfo.setName(appInfo.getName() + " " + result.getKey());
mAppAdapter.add(appInfo);
}
});
}
});
}複製程式碼
以上程式碼,建立了一個新的 Observable,它將會發射一個帶有 GroupedObservable
的序列。GroupedObservable
是一個特殊的 Observable,它源自一個分組的 key。在這個例子中,key就是String,代表的意思是 Month/Year 格式化的最近更新日期。
在研究這個函式的用法的時候,通過打 log 一度進入了迷惑的狀態,後通過 google 搜尋找到了該篇文章來更形象的描述了其實現原理,強烈推薦閱讀此文章。
buffer()
buffer()
函式將源 Observable 變換一個新的 Observable,這個新的 Observable 每次發射一組列表值而不是一個一個發射。
buffer(count = ?)
以下為該函式的示意圖:
上圖中展示了 buffer()
如何將 count 作為一個引數來指定有多少資料項被包在發射的列表中。實際上,buffer()
函式有幾種變體。其中有一個是允許你指定一個 skip 值:此後每 skip 項資料,然後又用 count 項資料填充緩衝區。如下圖所示:
buffer()
帶一個 timespan 的引數,會建立一個每隔 timespan 時間段就會發射一個列表的 Observable。
private void performBuffer() {
mAppAdapter.clear();
getApps().take(8)
.doOnNext(new Action1<AppInfo>() {
@Override
public void call(AppInfo appInfo) {
mAppAdapter.add(appInfo);
}
})
.flatMap(new Func1<AppInfo, Observable<String>>() {
@Override
public Observable<String> call(AppInfo appInfo) {
long time = (long) (Math.random() * 3 + 0.5);
LogUtils.d(TAG, "app: " + appInfo.getName() + " time: " + time);
return Observable.just(appInfo.getName())
.delay(time, TimeUnit.SECONDS);
}
})
.buffer(2, TimeUnit.SECONDS)
.subscribe(new Action1<List<String>>() {
@Override
public void call(List<String> strings) {
LogUtils.d(TAG, "values: " + Arrays.toString(strings.toArray()));
}
});
}複製程式碼
log 資訊為:
30843-30843/com.zhgqthomas.rxjava D/github_TransformObserv: app: Phone time: 0
30843-30843/com.zhgqthomas.rxjava D/github_TransformObserv: app: Email time: 2
30843-30843/com.zhgqthomas.rxjava D/github_TransformObserv: app: Contacts time: 0
30843-30843/com.zhgqthomas.rxjava D/github_TransformObserv: app: Settings time: 2
30843-30843/com.zhgqthomas.rxjava D/github_TransformObserv: app: Messages time: 2
30843-30843/com.zhgqthomas.rxjava D/github_TransformObserv: app: i Music time: 2
30843-30843/com.zhgqthomas.rxjava D/github_TransformObserv: app: i Theme time: 3
30843-30843/com.zhgqthomas.rxjava D/github_TransformObserv: app: Recorder time: 2
30843-30983/com.zhgqthomas.rxjava D/github_TransformObserv: values: [Phone, Contacts]
30843-30988/com.zhgqthomas.rxjava D/github_TransformObserv: values: [Email, Settings, Messages, i Music, Recorder, i Theme]複製程式碼
window()
window()
函式和 buffer()
很像,但是它發射的是 Observable 而不是列表。下圖展示了 window()
如何快取3個資料項並把它們作為一個新的 Observable 發射出去。
private void performWindow() {
mAppAdapter.clear();
getApps().take(8)
.doOnNext(new Action1<AppInfo>() {
@Override
public void call(AppInfo appInfo) {
mAppAdapter.add(appInfo);
}
})
.flatMap(new Func1<AppInfo, Observable<String>>() {
@Override
public Observable<String> call(AppInfo appInfo) {
long time = (long) (Math.random() * 3 + 0.5);
LogUtils.d(TAG, "app: " + appInfo.getName() + " time: " + time);
return Observable.just(appInfo.getName())
.delay(time, TimeUnit.SECONDS);
}
})
.window(2, TimeUnit.SECONDS)
.subscribe(new Action1<Observable<String>>() {
@Override
public void call(Observable<String> result) { // 與 buffer 的不同在於返回的是個 Observable
result.toList()
.subscribe(new Action1<List<String>>() {
@Override
public void call(List<String> strings) {
LogUtils.d(TAG, "values: " + Arrays.toString(strings.toArray()));
}
});
}
});
}複製程式碼
log 資訊為
28277-28277/com.zhgqthomas.rxjava D/github_TransformObserv: app: Phone time: 1
28277-28277/com.zhgqthomas.rxjava D/github_TransformObserv: app: Email time: 0
28277-28277/com.zhgqthomas.rxjava D/github_TransformObserv: app: Contacts time: 1
28277-28277/com.zhgqthomas.rxjava D/github_TransformObserv: app: Settings time: 2
28277-28277/com.zhgqthomas.rxjava D/github_TransformObserv: app: Messages time: 0
28277-28277/com.zhgqthomas.rxjava D/github_TransformObserv: app: i Music time: 3
28277-28277/com.zhgqthomas.rxjava D/github_TransformObserv: app: i Theme time: 2
28277-28277/com.zhgqthomas.rxjava D/github_TransformObserv: app: Recorder time: 1
28277-28442/com.zhgqthomas.rxjava D/github_TransformObserv: values: [Email, Messages, Phone, Contacts, Recorder]
28277-28445/com.zhgqthomas.rxjava D/github_TransformObserv: values: [Settings, i Theme, i Music]複製程式碼
cast()
以下為該函式的示意圖:
cast()
函式是 map()操作符的特殊版本。不同的地方在於map操作符可以通過自定義規則,把一個值A1變成另一個值A2,A1和A2的型別可以一樣也可以不一樣;而cast操作符主要是做型別轉換的,傳入引數為型別class,如果源Observable產生的結果不能轉成指定的class,則會丟擲ClassCastException執行時異常。