注:本系列文章主要用于博主個人學習記錄,本文末尾附上了一些較好的文章提供學習。
轉載請附 原文鏈接
RxJava學習系列(一)--使用
RxJava學習系列(二)--原理
RxJava利用響應式編程思想,專注于異步任務的處理,通過操作符進行流式操作,可以極大的去除多層嵌套,達到邏輯的簡潔明了。
舉個栗子??
RxJava的觀察者模式與普通觀察者模式有一個區別是分為“冷”啟動和“熱”啟動,“熱”啟動即不管有沒有觀察者,觀察者會按照自己的邏輯發送數據,而“冷”啟動則是只有當觀察者開啟訂閱時才開始發送數據。
1.基本概念及用法
-
三個重要的對象
- Observable-數據發送者
- Subscriber-訂閱者
- OnSubscribe-事件
一次事件訂閱的流程:Observable持有一個 OnSubscribe對象,事件在OnSubscribe對象中被執行,當有Subscriber訂閱了這個 Observable時,OnSubscribe中的事件開始執行,并由Observable發射數據給Subscriber
-
Subscriber對象
Subscriber是一個抽象類,需要實現三個方法
-
onCompleted()
: 事件隊列完結。RxJava 不僅把每個事件單獨處理,還會把它們看做一個隊列。RxJava 規定,當不會再有新的onNext()
發出時,需要觸發onCompleted()
方法作為標志。 -
onError()
: 事件隊列異常。在事件處理過程中出異常時,onError()
會被觸發,同時隊列自動終止,不允許再有事件發出。 - 在一個正確運行的事件序列中,
onCompleted()
和onError()
有且只有一個,并且是事件序列中的最后一個。需要注意的是,onCompleted()
和onError()
二者也是互斥的,即在隊列中調用了其中一個,就不應該再調用另一個。
引用自扔物線
在代碼中調用了onComplete后再調用onNext,依然可以發送數據,onComplete會在發送完所有的數據后才被調用
當onError被調用了,即使在出現錯誤之前調用onNext依然不會成功,只會觸發onError
?
Subscriber<String> subscriber = new Subscriber<String>() { @Override public void onCompleted() { //數據發送完畢 } @Override public void onError(Throwable e) { //數據發送出錯 } @Override public void onNext(String data) { //數據發送成功 } };
通過泛型指定Subscriber所能接收的數據,在onNext 中處理相應的邏輯,此處需要注意的是: onNext方法調用的次數取決于OnSubscribe中被調用的次數
-
Action1<T>
在某些情況下我們不需要在每一個回調中都處理邏輯,可能只需要訂閱onNext,就可以實現Action1<T>
new Action1<Object>() { @Override public void call(Object obj) { //有參數的回調onError和onNext } }; new Action0(){ @override public void call(){ //onComplete } };
-
-
Observable對象
- Observable.create(OnSubscribe<T> onSubscribe);
create方法傳入一個OnSubscribe對象,在call方法中發送數據,這是最基礎的創建方法
Observable.create(new Observable.OnSubscribe<String>() { @Override public void call(Subscriber<? super String> subscriber) { subscriber.onNext("create success"); } });
- Observable.just( T1 t1, T2 t2, T3 t3,…)
just方法允許快速創建隊列,每一個參數會調用onNext方法傳遞一次(最多10個),且按順序發送,just在發送完數據后,會調用onComplete
Observable.just("one","two","three");
- from
from可以將數組,Iterable,Future對象轉換為 Observable對象,發送數據
- Javadoc: from(array)
- Javadoc: from(Iterable)
- Javadoc: from(Future)
- Javadoc: from(Future,Scheduler)
- Javadoc: from(Future,timeout, timeUnit)
String [] array = new String[]{"one-from","two-from","three-from"}; Observable.from(array);
創建一個Observable的方法有很多,不一一列舉
創建Observable
-
訂閱事件
- Observable.subcribe()--return Subscription;
通過subscribe來開啟訂閱,此時Observable開始發送數據,并且返回一個Subscription對象
- Subscription
Subscription是一個接口,有兩個方法unsubscribe()和isUnsubscribe(),在訂閱事件時返回這個對象,可以在需要的時候取消掉訂閱,在android開發中能簡單有效的避免內存溢出。
2.線程控制
上面所提到的訂閱會默認在當前線程中執行,然并卵,既然是專注于異步操作,就一定有線程控制的方法
-
Schedulers—線程調度
Sckedulers的各種線程
subscribeOn—被觀察事件執行線程(事實上,在該方法調用之前,以及調用后,observeOn之前的代碼都會在subscribeOn所在的線程中執行)
observeOn— 觀察線程(可以多次轉換,observeOn指定在它之后的代碼線程)
-
實踐
該方法只應該被調用一次,如果調用多次,只有第一個會生效 !
多次調用subscribeOn
如上圖,首先指定了subscribeOn的線程為io線程,然后又指定了計算線程,打印日志
通過日志打印可以發現,只有第一個subscribeOn生效,并且在observeOn之前的代碼也都在io線程中執行,而在observeOn之后的代碼,在每一次調用該方法后都改變了線程
有好事的同學說了,那如果我先調用observeOn再調用subscribeOn呢?雖然沒有人這么做,但嚴謹的我還是要試試
先調用observeOn指定為主線程,然后subscribeOn指定為ui線程
可以看到第一條日志在io線程中執行,而第二條日志在主線程中,似乎可以得到一個結論
observeOn指定在它之后的代碼的執行線程,而其余代碼均在第一個subscribeOn指定的線程中執行
3.操作符
A.轉換操作
-
map()— 將發射的數據轉化為subscriber所需要的數據
Observable.create(new Observable.OnSubscribe<Integer>() { @Override public void call(Subscriber<? super Integer> subscriber) { subscriber.onNext(random()); subscriber.onCompleted(); } }).map(new Func1<Integer, String>() { @Override public String call(Integer integer){ if(integer == 0){ return "the number is 1"; }else{ return "the number is not 1"; } } });
栗子中的代碼是在原始的Observable中發射類型為Integer的數據,通過map操作后,subscriber所接收到的數據為 String 類型,map中需要傳入Fun1<T,E>,參數1表示上一個操作符操作后所發送的數據,一個Observable可以進行多次轉化操作,subscriber接收到的數據為最后一次轉化發射的數據。
注意:map操作轉換的是發射的數據,Observable本身并不會被轉換
-
flatmap()— 將一個Observable轉換為一個新的Observable,并且由這個新的Observable發射數據
Observable.create(new Observable.OnSubscribe<Integer>() { @Override public void call(Subscriber<? super Integer> subscriber) { subscriber.onNext(random()); subscriber.onCompleted(); } }).flatMap(new Func1<Integer, Observable<String>>() { @Override public Observable<String> call(Integer integer) { return Observable.just("new Observable"+integer); } });
flatMap 同樣需要傳入Func1<T,E> 與map不同的是,返回的是一個Observable對象,而subscriber所訂閱的應該是這個新的Observable,flatmap也可以多次調用多次轉換,問題來了~subscriber只關心接收到的數據,并不關心訂閱的具體是哪一個Observable,那flatMap和map的應用場景是什么呢?
-
應用場景
- map比較簡單,一個Observable可能被多個subscriber訂閱,而不同的訂閱所需要的最終數據不同,但事件的操作邏輯是相同的,就可以利用map來滿足不同的數據需求
-
flatmap的用處就比較多了,文章最開頭舉的栗子,一次復雜的注冊邏輯,首先要請求服務器獲取token,獲取token后注冊請求,注冊請求完成后,登錄請求,每一次請求利用Retrofit封裝返回一個 Observable對象.我們只關心最后登錄成功后告知用戶,并刷新UI。這樣原本用回調至少嵌套兩次的邏輯,變得清晰明了(這樣的注冊邏輯本身是有問題的~)
模擬一次注冊請求
![Uploading 48862518-1B43-457E-B734-36AE6669893C_441752.jpg . . .]
注意:只有每一個Observable都成功發射數據后,才會調用onNext方法,如果出現異常會直接調用onError
這樣看來,好像很雞肋,后面會講到錯誤操作,你會發現RxJava確實是很牛逼的啊~
-
其它轉換操作
轉換操作符
B.合并操作
-
merge— 將多個Observable合并為一個Observable發射數據
Observable.merge(observable1,observable2,observable3,observable4);
官方文檔說明:merge可能會導致交錯發射數據,即不是按照合并順序來發射數據
同樣,一旦有一個Observable發射異常,會立即觸發onError,RxJava的實現中有一個mergeDelayError— 只有當所有的數據都發射完畢后才會調用onError
-
concat— 將多個Observable合并為一個Observable并且按順序發射數據
Observable.concat(observable1,observable2,observable3,observable4)
-
zip— 將多個Observables的發射物結合到一起,基于這個函數的結果為每個結合體發射單個數據項。
Observable.zip( Observable.just("1"), Observable.just("2"), Observable.just("3"), Observable.just("4"), new Func4<String, String, String, String, String>() { @Override public String call(String s, String s2, String s3, String s4) { return s+s2+s3+s4; } });
zip傳入需要合并的Observable對象,以及 Func4<T,...,Object>,與merge不同的是,zip是將所有發射的數據拿到后,進行整合,最后發射這個整合后的數據。call中的參數是嚴格按照合并順序所發射的數據,return的即為最終發射的數據
zip的不僅可以合并發射源,并且可以根據需要轉換最終發射的數據類型
C.過濾操作
假設這樣一種場景,加載數據的時候先向服務器請求,如果成功就顯示,如果失敗就查找緩存數據。很容易想到可以利用合并操作符來處理,但是合并操作會依次發射數據,這不是我們所希望的。這里就需要用的過濾操作了
-
filter — 只發射通過了謂詞測試的數據項
filter根據規則去檢驗數據,只要通過了檢驗的數據都會被發射,直到檢驗完最后一個 Observable
.filter(new Func1<Integer, Boolean>() { @Override public Boolean call(Integer i) { //只發射小于等于5的數據 return i<=5; } })
-
first — 只發射第一項(或者滿足某個條件的第一項)數據
first類似于filter,不同的是,只發射第一個通過檢驗的數據
first()//只發射第一個數據 first(Func1)//滿足某個條件的第一個發射成功的數據 .first(new Func1<String, Boolean>() { @Override public Boolean call(String s) { return false; } })
所有發射的數據,會在call中按照規則進行檢驗,比如當第一個傳過來的字符串不為空時,就認為發射數據成功,那么應該return true,當return為true的時候會調用onNext方法,而還沒有發射數據的Observable將不再發射數據,如果return為false,那么會依次檢驗后面的Observable是否發射數據成功,直到return true或者全部不符合調onError
-
last — 只發射最后一條(或者滿足某個條件的最后一項)數據
last的用法跟frist一毛一樣。
-
其它過濾操作
過濾操作符
D.異常處理
onErrorReturn可以在異常發生時發射一個默認的數據,結合過濾操作,可以發射一個不符合規則的數據,避免中斷數據發射
E.doOn
有一種場景,比如說請求到數據后寫入緩存,但是不希望訂閱者去處理,因為如果多處訂閱必然會產生重復代碼并且可能阻塞主線程,doOn的系列操作就派上了用場
-
doOnNext — 當數據發射成功時調用
Observable.just("data to shoot") .doOnNext(new Action1<String>() { @Override public void call(String s) { //發射成功后需要的操作 writeToCache(s); } })
在使用的時候注意判斷doOnNext當前在哪個線程執行
- doOnError — 當發生異常時調用
- doOnSubscribe — 當被訂閱時調用
- doOnTerminate — 發射數據完畢后調用
F.封裝-compose
當我們真正開始使用RxJava來替換之前的邏輯代碼時,我們發現僅用現有的操作符無法做到完全的簡潔,依然會出現一些不必要的重復代碼和邏輯。適度的封裝也是必要的,RxJava早就想到了這點,提供了一個操作符來封裝一些通用代碼
-
compose(@NotNull Transformer<T,R>)
compose方法需要傳入一個變形金剛對象,其中泛型T為原始的Observable所發射的數據類型,R為變形后所發射的數據類型,舉個栗子,比如封裝一個方法在io線程中發射數據,在ui線程中觀察接收數據。
public <T> Observable.Transformer<T,T> io_main(){ return new Observable.Transformer<T, T>() { @Override public Observable<T> call(Observable<T> tObservable) { return tObservable.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()); } }; }
創建一個Transformer對象,需要實現call方法,return一個新的Observable,然后傳入到compose()中:
Observable.just("123") .compose(this.<String>io_main()) .subscribe();
查看一下compose的源碼發現,它其實就做了一件事情,調用 Transformer的call方法,并把當前的Observable對象作為參數傳進去,返回call方法得到的新的Observable
public <R> Observable<R> compose(Transformer<? super T, ? extends R> transformer) { return ((Transformer<T, R>) transformer).call(this); }
?
4.RxBus
EventBus讓模塊間的耦合更低,利用 RxJava實現EventBus簡直是容易,且便于管理
在實現RxBus之前介紹兩個很重要的類
-
CompositeSubscription— Subscription that represents a group of Subscriptions that are unsubscribed together.
是Subscription的一個實習類,用于管理一組訂閱,當取消訂閱時,會將這一組訂閱全部取消,在Android中可以利用該類管理一個Activity中所有的異步任務,當Activity被銷毀時,取消訂閱,避免內存泄漏
- add — 將一個訂閱加入到一個訂閱組中
- remove — 將一個訂閱從該組中移除
- clear — 清空訂閱組
- unsubscribe — 取消改組中正在進行的所有訂閱
-
Subject — 一個神奇的類,在 RxJava中它同時充當了Observer和Observable的角色。
文章一開頭提到了“冷”啟動和“熱”啟動,而一個Subject可以將一個“冷”Observer變成一個“熱”的,因為它是一個Observer,它可以訂閱一個或多個Observable;又因為它是一個Observable,它可以轉發它收到(Observe)的數據,也可以發射新的數據。
subject.subscribe(subscriber) — 訂閱事件
subject.onNext(obj) — 發射數據
Subject 在RxJava中總共有7個子類,這里不一一介紹(因為我也沒用過…)
-
PublishSubject — 一個“熱”的Observable,這個對象會在onNext被調用的時候就開始發射數據,無論有沒有訂閱者,當一個Observer訂閱了這個對象時,只會收到訂閱時間點之后所發射的數據。官方給出的栗子:
兩個observer分別訂閱了同一個subject,observer1會收到所有的數據,而observer2只能收到最后一條數據
PublishSubject<Object> subject = PublishSubject.create(); // observer1 will receive all onNext and onCompleted events subject.subscribe(observer1); subject.onNext("one"); subject.onNext("two"); // observer2 will only receive "three" and onCompleted subject.subscribe(observer2); subject.onNext("three"); subject.onCompleted();
再想想EventBus的原理,我們所需要的正是這樣一個“熱”Observable。這里是較為復雜的一種實現,先上原理圖
RxBus原理 -
再貼代碼
private ConcurrentHashMap<Object, List<Subject>> subjectMapper
= new ConcurrentHashMap<Object, List<Subject>>();
//訂閱事件
public <T> Observable<T> subscribe(@NonNull Object tag) {
List<Subject> subjectList = subjectMapper.get(tag);
if (null == subjectList) {
subjectList = new ArrayList<Subject>();
subjectMapper.put(tag, subjectList);
}
Subject<T, T> subject;
subjectList.add(subject = PublishSubject.create());
return subject;
}
//發布事件--發射數據
public void post(@NonNull Object tag, @NonNull Object content) {
List<Subject> subjectList = subjectMapper.get(tag);
if (!isEmpty(subjectList)) {
for (Subject subject : subjectList) {
subject.onNext(content);
}
}
}
RxBus的核心邏輯就完成了,當然還需要加上取消訂閱,清空事件等代碼,比較簡單不再贅述,在我實際的項目開發中,我將 RxBus交由 RxJavaManager進行管理,所有的訂閱事件全部經過 RxJavaManager來操作,在需要取消訂閱的地方統一unsubscribe
5.資料
- RxDocs — ReactiveX中文文檔
- 給 Android 開發者的 RxJava 詳解 — 扔物線大神的文章
- 什么是函數響應式編程 — android中響應式編程介紹
- T-MVP — 一個不錯的開源項目 RxJava+MVP
- ReactiveX.io — 官方網站
- 從 RxBus 這輛蘭博基尼深入進去 — 我的好基友謝三弟的Subject源碼分析