給初學者的RxJava2.0教程(三)
前言
原文鏈接:http://www.jianshu.com/p/128e662906af
上一節講解了線程調度, 並且舉了兩個實際中的例子, 其中有一個登錄的例子, 不知大家有沒有想過這麼一個問題, 如果是一個新用戶, 必須先註冊, 等註冊成功之後再自動登錄該怎麼做呢.
很明顯, 這是一個嵌套的網絡請求, 首先需要去請求註冊, 待註冊成功回調了再去請求登錄的接口.
我們當然可以想當然的寫成這樣:
private void login() {
api.login(new LoginRequest())
.subscribeOn(Schedulers.io()) //在IO線程進行網絡請求
.observeOn(AndroidSchedulers.mainThread()) //回到主線程去處理請求結果
.subscribe(new Consumer<LoginResponse>() {
@Override
public void accept(LoginResponse loginResponse) throws Exception {
Toast.makeText(MainActivity.this, "登錄成功", Toast.LENGTH_SHORT).show();
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
Toast.makeText(MainActivity.this, "登錄失敗", Toast.LENGTH_SHORT).show();
}
});
}
private void register() {
api.register(new RegisterRequest())
.subscribeOn(Schedulers.io()) //在IO線程進行網絡請求
.observeOn(AndroidSchedulers.mainThread()) //回到主線程去處理請求結果
.subscribe(new Consumer<RegisterResponse>() {
@Override
public void accept(RegisterResponse registerResponse) throws Exception {
Toast.makeText(MainActivity.this, "註冊成功", Toast.LENGTH_SHORT).show();
login(); //註冊成功, 調用登錄的方法
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
Toast.makeText(MainActivity.this, "註冊失敗", Toast.LENGTH_SHORT).show();
}
});
}
(其實能寫成這樣的代碼的人也很不錯了, 至少沒寫到一起...)
這樣的代碼能夠工作, 但不夠優雅, 通過本節的學習, 可以讓我們用一種更優雅的方式來解決這個問題.
正題
先來看看最簡單的變換操作符map吧
Map
map是RxJava中最簡單的一個變換操作符了, 它的作用就是對上游發送的每一個事件應用一個函數, 使得每一個事件都按照指定的函數去變化. 用事件圖表示如下:
圖中map中的函數作用是將圓形事件轉換爲矩形事件, 從而導致下游接收到的事件就變爲了矩形.用代碼來表示這個例子就是:
Observable.create(new ObservableOnSubscribe<Integer>() {
@Override
public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
emitter.onNext(1);
emitter.onNext(2);
emitter.onNext(3);
}
}).map(new Function<Integer, String>() {
@Override
public String apply(Integer integer) throws Exception {
return "This is result " + integer;
}
}).subscribe(new Consumer<String>() {
@Override
public void accept(String s) throws Exception {
Log.d(TAG, s);
}
});
在上游我們發送的是數字類型, 而在下游我們接收的是String類型, 中間起轉換作用的就是map操作符, 運行結果爲:
D/TAG: This is result 1
D/TAG: This is result 2
D/TAG: This is result 3
通過Map, 可以將上游發來的事件轉換爲任意的類型, 可以是一個Object, 也可以是一個集合, 如此強大的操作符你難道不想試試?
接下來我們來看另外一個廣爲人知的操作符flatMap.
FlatMap
flatMap是一個非常強大的操作符, 先用一個比較難懂的概念說明一下:
FlatMap
將一個發送事件的上游Observable變換爲多個發送事件的Observables,然後將它們發射的事件合併後放進一個單獨的Observable裏.
這句話比較難以理解, 我們先通俗易懂的圖片來詳細的講解一下, 首先先來看看整體的一個圖片:
先看看上游, 上游發送了三個事件, 分別是1,2,3, 注意它們的顏色.
中間flatMap的作用是將圓形的事件轉換爲一個發送矩形事件和三角形事件的新的上游Observable.
還是不能理解? 別急, 再來看看分解動作:
這樣就很好理解了吧 !!!
上游每發送一個事件, flatMap都將創建一個新的水管, 然後發送轉換之後的新的事件, 下游接收到的就是這些新的水管發送的數據. 這裏需要注意的是, flatMap並不保證事件的順序, 也就是圖中所看到的, 並不是事件1就在事件2的前面. 如果需要保證順序則需要使用concatMap
.
說了原理, 我們還是來看看實際中的代碼如何寫吧:
Observable.create(new ObservableOnSubscribe<Integer>() {
@Override
public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
emitter.onNext(1);
emitter.onNext(2);
emitter.onNext(3);
}
}).flatMap(new Function<Integer, ObservableSource<String>>() {
@Override
public ObservableSource<String> apply(Integer integer) throws Exception {
final List<String> list = new ArrayList<>();
for (int i = 0; i < 3; i++) {
list.add("I am value " + integer);
}
return Observable.fromIterable(list).delay(10,TimeUnit.MILLISECONDS);
}
}).subscribe(new Consumer<String>() {
@Override
public void accept(String s) throws Exception {
Log.d(TAG, s);
}
});
如代碼所示, 我們在flatMap中將上游發來的每個事件轉換爲一個新的發送三個String事件的水管, 爲了看到flatMap結果是無序的,所以加了10毫秒的延時, 來看看運行結果吧:
D/TAG: I am value 1
D/TAG: I am value 1
D/TAG: I am value 1
D/TAG: I am value 3
D/TAG: I am value 3
D/TAG: I am value 3
D/TAG: I am value 2
D/TAG: I am value 2
D/TAG: I am value 2
結果也確實驗證了我們之前所說.
這裏也簡單說一下concatMap
吧, 它和flatMap的作用幾乎一模一樣, 只是它的結果是嚴格按照上游發送的順序來發送的, 來看個代碼吧:
Observable.create(new ObservableOnSubscribe<Integer>() {
@Override
public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
emitter.onNext(1);
emitter.onNext(2);
emitter.onNext(3);
}
}).concatMap(new Function<Integer, ObservableSource<String>>() {
@Override
public ObservableSource<String> apply(Integer integer) throws Exception {
final List<String> list = new ArrayList<>();
for (int i = 0; i < 3; i++) {
list.add("I am value " + integer);
}
return Observable.fromIterable(list).delay(10,TimeUnit.MILLISECONDS);
}
}).subscribe(new Consumer<String>() {
@Override
public void accept(String s) throws Exception {
Log.d(TAG, s);
}
});
只是將之前的flatMap改爲了concatMap, 其餘原封不動, 運行結果如下:
D/TAG: I am value 1
D/TAG: I am value 1
D/TAG: I am value 1
D/TAG: I am value 2
D/TAG: I am value 2
D/TAG: I am value 2
D/TAG: I am value 3
D/TAG: I am value 3
D/TAG: I am value 3
可以看到, 結果仍然是有序的.
好了關於RxJava的操作符最基本的使用就講解到這裏了, RxJava中內置了許許多多的操作符, 這裏通過講解map
和flatMap
只是起到一個拋磚引玉的作用, 關於其他的操作符只要大家按照本文的思路去理解, 再仔細閱讀文檔, 應該是沒有問題的了, 如果大家有需要也可以將需要講解的操作符列舉出來, 我可以根據大家的需求講解一下.
實踐
學習了FlatMap操作符, 我們就可以回答文章開頭提出的那個問題了.
如何優雅的解決嵌套請求, 只需要用flatMap轉換一下就行了.
先回顧一下上一節的請求接口:
public interface Api {
@GET
Observable<LoginResponse> login(@Body LoginRequest request);
@GET
Observable<RegisterResponse> register(@Body RegisterRequest request);
}
可以看到登錄和註冊返回的都是一個上游Observable, 而我們的flatMap操作符的作用就是把一個Observable轉換爲另一個Observable, 因此結果就很顯而易見了:
api.register(new RegisterRequest()) //發起註冊請求
.subscribeOn(Schedulers.io()) //在IO線程進行網絡請求
.observeOn(AndroidSchedulers.mainThread()) //回到主線程去處理請求註冊結果
.doOnNext(new Consumer<RegisterResponse>() {
@Override
public void accept(RegisterResponse registerResponse) throws Exception {
//先根據註冊的響應結果去做一些操作
}
})
.observeOn(Schedulers.io()) //回到IO線程去發起登錄請求
.flatMap(new Function<RegisterResponse, ObservableSource<LoginResponse>>() {
@Override
public ObservableSource<LoginResponse> apply(RegisterResponse registerResponse) throws Exception {
return api.login(new LoginRequest());
}
})
.observeOn(AndroidSchedulers.mainThread()) //回到主線程去處理請求登錄的結果
.subscribe(new Consumer<LoginResponse>() {
@Override
public void accept(LoginResponse loginResponse) throws Exception {
Toast.makeText(MainActivity.this, "登錄成功", Toast.LENGTH_SHORT).show();
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
Toast.makeText(MainActivity.this, "登錄失敗", Toast.LENGTH_SHORT).show();
}
});
從這個例子也可以看到我們切換線程是多麼簡單.
好了本次的教程就到這裏了. 下一節我們將會學到Flowable
以及理解Backpressure
背壓的概念. 敬請期待.