前言
之前寫RxJava相關文章的時候,就有人想讓我談談RxJava2.0的新特性,說實話,一開始我是拒絕的。因為在我看來,RxJava2.0雖然是版本的重大升級,但總歸還是RxJava,升級一個版本還能上天是咋的?瞭解一下它的更新文件不就好了麼?真的有必要單出一篇文章來談這個麼?
但是詳細的瞭解了RxJava2.0以及部分原始碼之後,我覺得還是有必要對RxJava2.0做一個說明,幫助大家對於RxJava有更好的認識。
鋪墊
假如你還不是很熟悉RxJava,或者對於背壓這個概念(2.0更新中會涉及到背壓的概念)很模糊,希望你也能讀一讀下面兩篇鋪墊的文章:
關於背壓的那篇文章本來是本文的一部分,因為篇幅過大,被剝離出去了,所以建議大家有時間也一併閱讀。
正文
RxJava2.0有很多的更新,一些改動甚至衝擊了我之前的文章裡的內容,這也是我想寫這篇文章的原因之一。不過想要寫這篇文章其實也是有難度的,因為相關的資料去其實是很少的,還得自己硬著頭皮上….不過俗話說得好,有困難要上,沒有困難創造困難也要上。
在這裡,我會按照我們之前關於RxJava的文章的講述順序:觀察者模式,操作符,執行緒排程,這三個方面依次看有哪些更新。
新增依賴
這個估計得放在最前面。
Android端使用RxJava需要依賴新的包名:
//RxJava的依賴包(我使用的最新版本)
compile `io.reactivex.rxjava2:rxjava:2.0.1`
//RxAndroid的依賴包
compile `io.reactivex.rxjava2:rxandroid:2.0.1`複製程式碼
觀察者模式
首先宣告,RxJava以觀察者模式為骨架,在2.0中依然如此。
不過此次更新中,出現了兩種觀察者模式:
- Observable(被觀察者)/Observer(觀察者)
- Flowable(被觀察者)/Subscriber(觀察者)
RxJava2.X中,Observeable用於訂閱Observer,是不支援背壓的,而Flowable用於訂閱Subscriber,是支援背壓(Backpressure)的。
關於背壓這個概念以及它在1.0版本中的缺憾在上一篇文章中我已經介紹到了,如果你不是很清楚,我在這裡在做一個介紹:背壓是指在非同步場景中,被觀察者傳送事件速度遠快於觀察者的處理速度的情況下,一種告訴上游的被觀察者降低傳送速度的策略,在1.0中,關於背壓最大的遺憾,就是集中在Observable這個類中,導致有的Observable支援背壓,有的不支援。為了解決這種缺憾,新版本把支援背壓和不支援背壓的Observable區分開來。
Observable/Observer
Observable正常用法:
Observable mObservable=Observable.create(new ObservableOnSubscribe<Integer>() {
@Override
public void subscribe(ObservableEmitter<Integer> e) throws Exception {
e.onNext(1);
e.onNext(2);
e.onComplete();
}
});
Observer mObserver=new Observer<Integer>() {
//這是新加入的方法,在訂閱後傳送資料之前,
//回首先呼叫這個方法,而Disposable可用於取消訂閱
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Integer value) {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
};
mObservable.subscribe(mObserver);複製程式碼
這種觀察者模型是不支援背壓的。
啥叫不支援背壓呢?
當被觀察者快速傳送大量資料時,下游不會做其他處理,即使資料大量堆積,呼叫鏈也不會報MissingBackpressureException,消耗記憶體過大隻會OOM
我在測試的時候,快速傳送了100000個整形資料,下游延遲接收,結果被觀察者的資料全部傳送出去了,記憶體確實明顯增加了,遺憾的是沒有OOM。
所以,當我們使用Observable/Observer的時候,我們需要考慮的是,資料量是不是很大(官方給出以1000個事件為分界線,僅供各位參考)
Flowable/Subscriber
Flowable.range(0,10)
.subscribe(new Subscriber<Integer>() {
Subscription sub;
//當訂閱後,會首先呼叫這個方法,其實就相當於onStart(),
//傳入的Subscription s引數可以用於請求資料或者取消訂閱
@Override
public void onSubscribe(Subscription s) {
Log.w("TAG","onsubscribe start");
sub=s;
sub.request(1);
Log.w("TAG","onsubscribe end");
}
@Override
public void onNext(Integer o) {
Log.w("TAG","onNext--->"+o);
sub.request(1);
}
@Override
public void onError(Throwable t) {
t.printStackTrace();
}
@Override
public void onComplete() {
Log.w("TAG","onComplete");
}
});複製程式碼
輸出如下:
onsubscribe start
onNext--->0
onNext--->1
onNext--->2
...
onNext--->9
onComplete
onsubscribe end複製程式碼
Flowable是支援背壓的,也就是說,一般而言,上游的被觀察者會響應下游觀察者的資料請求,下游呼叫request(n)來告訴上游傳送多少個資料。這樣避免了大量資料堆積在呼叫鏈上,使記憶體一直處於較低水平。
當然,Flowable也可以通過creat()來建立:
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> e) throws Exception {
e.onNext(1);
e.onNext(2);
e.onNext(3);
e.onNext(4);
e.onComplete();
}
}
//需要指定背壓策略
, BackpressureStrategy.BUFFER);複製程式碼
Flowable雖然可以通過create()來建立,但是你必須指定背壓的策略,以保證你建立的Flowable是支援背壓的(這個在1.0的時候就很難保證,可以說RxJava2.0收緊了create()的許可權)。
根據上面的程式碼的結果輸出中可以看到,當我們呼叫subscription.request(n)方法的時候,不等onSubscribe()中後面的程式碼執行,就會立刻執行到onNext方法,因此,如果你在onNext方法中使用到需要初始化的類時,應當儘量在subscription.request(n)這個方法呼叫之前做好初始化的工作;
當然,這也不是絕對的,我在測試的時候發現,通過create()自定義Flowable的時候,即使呼叫了subscription.request(n)方法,也會等onSubscribe()方法中後面的程式碼都執行完之後,才開始呼叫onNext。
TIPS: 儘可能確保在request()之前已經完成了所有的初始化工作,否則就有空指標的風險。
其他觀察者模式
當然,除了上面這兩種觀察者,還有一類觀察者
- Single/SingleObserver
- Completable/CompletableObserver
- Maybe/MaybeObserver
其實這三者都差不多,Maybe/MaybeObserver可以說是前兩者的複合體,因此以Maybe/MaybeObserver為例簡單介紹一下這種觀察者模式的用法
//判斷是否登陸
Maybe.just(isLogin())
//可能涉及到IO操作,放在子執行緒
.subscribeOn(Schedulers.newThread())
//取回結果傳到主執行緒
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new MaybeObserver<Boolean>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onSuccess(Boolean value) {
if(value){
...
}else{
...
}
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});複製程式碼
上面就是Maybe/MaybeObserver的普通用法,你可以看到,實際上,這種觀察者模式並不用於傳送大量資料,而是傳送單個資料,也就是說,當你只想要某個事件的結果(true or false)的時候,你可以用這種觀察者模式
這是上面那些被觀察者的上層介面:
//Observable介面
interface ObservableSource<T> {
void subscribe(Observer<? super T> observer);
}
//Single介面
interface SingleSource<T> {
void subscribe(SingleObserver<? super T> observer);
}
//Completable介面
interface CompletableSource {
void subscribe(CompletableObserver observer);
}
//Maybe介面
interface MaybeSource<T> {
void subscribe(MaybeObserver<? super T> observer);
}
//Flowable介面
public interface Publisher<T> {
public void subscribe(Subscriber<? super T> s);
}複製程式碼
其實我們可以看到,每一種觀察者都繼承自各自的介面,這也就把他們能完全的區分開,各自獨立(特別是Observable和Flowable),保證了他們各自的擴充或者配套的操作符不會相互影響。
例如flatMap操作符實現:
//Flowable中flatMap的定義
Flowable<R> flatMap(Function<? super T, ? extends Publisher<? extends R>> mapper);
//Observable中flatMap的定義
Observable<R> flatMap(Function<? super T, ? extends ObservableSource<? extends R>> mapper);複製程式碼
假如你想為Flowable寫一個自定義的操作符,那麼只要保證Function< Publisher >中的型別實現了Publisher介面即可。這麼說可能很抽象,大家不理解其實也沒關係,因為並不推薦大家自定義操作符,RxJava中的操縱符的組合已經可以滿足大家的需求了。
當然,你也會注意到上面那些介面中的subscribe()方法的返回型別為void了,在1.X中,這個方法一般會返回一個Subscription物件,用於取消訂閱。現在,這個功能的物件已經被放到觀察者Observer或者subscriber的內部實現方法中了,
Flowable/Subscriber
public interface Subscriber<T> {
public void onSubscribe(Subscription s);
public void onNext(T t);
public void onError(Throwable t);
public void onComplete();
}
public interface Subscription {
public void request(long n);
public void cancel();
}複製程式碼
上面的例項中,onSubscribe(Subscription s)傳入的引數s就肩負著取消訂閱的功能,當然,他也可以用於請求上游的資料。
在Observable/observer中,傳入的引數是另一個物件
Observable/Observer
public interface Observer<T> {
void onSubscribe(Disposable d);
void onNext(T value);
void onError(Throwable e);
void onComplete();
}
public interface Disposable {
/**
* Dispose the resource, the operation should be idempotent.
*/
void dispose();
/**
* Returns true if this resource has been disposed.
* @return true if this resource has been disposed
*/
boolean isDisposed();
}複製程式碼
在Observer介面中,onSubscribe(Disposable d)方法傳入的Disposable也是用於取消訂閱,基本功能是差不多的,只不過命名不一致,大家知道就好。
其實這種設計可以說還是符合邏輯的,因為取消訂閱這個動作就只有觀察者(Observer等)才能做的,現在把它併入到觀察者內部,也算順理成章吧。
最後再提一點更新,就是被觀察者不再接收null作為資料來源了。
操作符相關
這一塊其實可以說沒什麼改動,大部分之前你用過的操作符都沒變,即使有所變動,也只是包名或類名的改動。大家可能經常用到的就是Action和Function。
Action相關
之前我在文章裡介紹過關於Action這類介面,在1.0中,這類介面是從Action0,Action1…往後排(數字代表可接受的引數),現在做出了改動
Rx1.0———–Rx2.0
Action0——–Action
Action1——–Consumer
Action2——–BiConsumer
後面的Action都去掉了,只保留了ActionN
Function相關
同上,也是命名方式的改變
上面那兩個類,和RxJava1.0相比,他們都增加了throws Exception,也就是說,在這些方法做某些操作就不需要try-catch。
例如:
Flowable.just("file.txt")
.map(name -> Files.readLines(name))
.subscribe(lines -> System.out.println(lines.size()), Throwable::printStackTrace);複製程式碼
Files.readLines(name)這類io方法本來是需要try-catch的,現在直接丟擲異常,就可以放心的使用lambda ,保證程式碼的簡潔優美。
doOnCancel/doOnDispose/unsubscribeOn
以doOnCancel為例,大概就是當取消訂閱時,會呼叫這個方法,例如:
Flowable.just(1, 2, 3)
.doOnCancel(() -> System.out.println("Cancelled!"))
.take(2)
.subscribe(System.out::println);複製程式碼
take新操符會取消後面那些還未被髮送的事件,因而會觸發doOnCancel
其他的一些操作符基本沒變,或者只是改變了名字,在這裡就不一一介紹了,需要提一下的是,很多操作符都有兩套,一套用於Observable,一套用於Flowable。
執行緒排程
可以說這一塊兒基本也沒有改動,如果一定要說的話。
- 那就是去掉了Schedulers.immediate()這個執行緒環境
- 移除的還有Schedulers.test()(我好像從來沒用過這個方法)
- io.reactivex.Scheduler這個抽象類支援直接排程自定義執行緒任務(這個我也沒怎麼用)
補充
如果你想把自己的RxJava1.0的遷移到2.0的版本,可以使用這個庫RxJava2Interop,它可以在Rxjava1.0和2.0之間相互轉換,也就是說,不僅可以把1.0的程式碼遷移到2.0,你還可以把2.0的程式碼遷移到1.0,哈哈。
補充2
在RxJava1.0中,有的人會使用CompositeSubscription來收集Subscription,來統一取消訂閱,現在在RxJava2.0中,由於subscribe()方法現在返回void,那怎麼辦呢?
其實在RxJava2.0中,Flowable提供了subscribeWith這個方法可以返回當前訂閱的觀察者,並且通過ResourceSubscriber DisposableSubscriber等觀察者來提供 Disposable的介面。
所以,如果想要達成RxJava1.0的效果,現在應該是這樣做:
CompositeDisposable composite = new CompositeDisposable();
composite.add(Flowable.range(1, 8).subscribeWith(subscriber));
這個subscriber 應該是 ResourceSubscriber 或者 DisposableSubscriber 的例項。
結尾
其實從整篇文章的分析來看,改動最大的還是觀察者模式的實現,被拆分和細化了,主要分成了Observable和Flowable兩大類,當然還有與之相關聯的其他變動,總體來看這一版本可以說是對於觀察者和被觀察者的重構。
RxJava2.0的範例程式碼我沒精力去寫了,也正巧有位外國朋友已經寫了RxJava2.0的demo,下面是他的專案地址:
當然,學習2.0 的過程中有什麼問題也可以在這裡留言討論。
後記
這篇文章半個月前就開始寫了,但是一直不太滿意,所以在草稿箱裡躺了很久,但是本著不放棄任何一篇落後文章的信念,還是振作起來,完成關於RxJava2.0的介紹。你可能不信,寫完頓時有了一種解(xu)脫的感覺。
身體被掏空…
附錄
下面我截圖展示一下2.0相對於1.0的一些改動的細節,僅做參考。
其實這些都是官方給出的列表,截圖在這裡只是方便大家觀摩。