RxJava
RxJava
Github的wiki的介紹:
RxJava
is a Java VM implementation of Reactive Extensions: a library for composing asynchronous and event-based programs by using observable sequences.
大概意思是RxJava
是Java VM
上一個使用可觀測序列來組成的一個異步的、基于事件的庫。
從這么一句話,看來有兩個關鍵點: ① 異步 ② 事件 ③可觀測 。 概括性的話總是那么抽象,看了這句話,還是不清楚,RxJava
到底是干什么的。一個亙古不變的道理, 實踐出真理。只有你用了,你才知道它是什么。
在我看來,RxJava
就是一個異步的擴展的觀察者模式。區別于AsynTask
以及Handler的優點在于代碼簡潔,鏈式調用。但是注意啦,我這里說的代碼簡潔,可不是說它的代碼量少,而是因為它邏輯清晰簡潔,當你回顧代碼的時候,可以方便的看出當初寫的邏輯。而不像Handler,要找到發送message的地方,以及接受message的地方, 更有各種縮進讓你一番好找,不方便代碼的回溯。而RxJava
的鏈式調用,使整體代碼結構看起來很清晰。
RxJava
的神奇之處不僅在于異步的觀察者模式,更強大的在于它的操作符,它提供了大量的操作符來幫助你更好的解決復雜的邏輯。結合Retrofit
可以很方便的嵌套請求網絡,解決類似當你要獲取服務器上面的數據的時候,首先要登錄,而登錄之前要先獲取服務器端的一個認證挑戰字。代碼如下:
InnerServerApi.requestLoginCode(mLoginCodeUrl, mAccount).subscribeOn(Schedulers.io()).observeOn(Schedulers.io())
.flatMap(new Func1<LoginCode, Observable<AAAData>>() {
@Override
public Observable<AAAData> call(final LoginCode loginCode) {
Log.d(TAG, loginCode.getLoginCode() + "HAHAHA");
return InnerServerApi.requestLoginData(mAAALoginUrl, loginCode.getLoginCode(), mAccount, mPassword);
}
}).subscribeOn(Schedulers.io()).subscribe(new Subscriber<AAAData>() {
@Override
public void onCompleted() {
Logger.i(TAG, "onCompleted");
}
@Override
public void onError(Throwable e) {
Log.d(TAG, "login:onFailure");
}
@Override
public void onNext(AAAData loginData) {
Log.d(TAG, "login:onSuccess:");
}
});
我們不說代碼量是否多了,反正邏輯是清晰了,這才是我們追求的。 如果采用AsyncTask
避免不了的CallBack嵌套,嵌套到回溯起來,自己一時半會都反應不過來呢。 這就是RxJava迷人地方之一,也是最迷人的操作符。下面,我會講一些比較通用的操作符。更多操作符也可以查看API,里面有圖片為你展示事件序列經過操作符之后的流向。
下面就開始學習基本概念啦。。。
RxJava的四個關鍵
-
Observable
被觀察者,事件的發出者。 -
Observer
觀察者,被動觀察事件的發出并做相應的處理。 通常我們都是用Subscriber
,Subscriber
是繼承自Observer
的一個類,其實就算你實例化的是一個Observer對象,底層也是用Subscriber包裹了這個Observer對象來實現相應的邏輯的,所以可以直接使用Subscriber
。 - event 事件 被觀察者發出的事件
- 訂閱 (
Subscribe
),觀察者訂閱被觀察者,即觀察者監聽被觀察者的動態,一旦被觀察者發出了什么動作就傳送到了觀察者那里,舉個例子,就像我們關注某個微信公眾號,關注這個動作就相當于 訂閱, 然后微信公眾號有新的消息推送,我們就可以看到。我們就是觀察者,公眾號就是被我們觀察的對象。
通過這四個關鍵,更加說明了它是一個異步的擴展的觀察者模式。
RxJava 的家長里短
RxJava的功能強大在于通過操作符變換,線程調度靈活的實現各種事件序列的處理,我覺得Observable的每一個方法都可以描述成一個操作符,下面就用操作符來說。
RxJava的觀察者模式如圖:
簡單使用模板
使用RxJava
基本的步驟是下面三步:
創建被觀察者
Observable
創建一個Observable的方法有很多個。其中一個就是create,有關于更多的創建操作符,創建型操作符 點這個可以了解到。
Observable observable = Observable.create(new Observable.OnSubscribe<Integer>() {
@Override
public void call(Subscriber<? super Integer> subscriber) {
try {
subscriber.onNext(3);
subscriber.onNext(4);
subscriber.onCompleted();
} catch (Exception e) {
subscriber.onError(e);
}
}
})
call
方法里面是被觀察者訂閱之后要向觀察者發出的事件。
創建觀察者
Subscriber subscriber = new Subscriber<Integer>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
Log.i(TAG, e.toString());
}
@Override
public void onNext(Integer integer) {
Log.i(TAG, integer + "");
}
}
onCompleted
是整個事件序列結束之后調用的方法。在一個正確運行的事件序列中,onCompleted()
和 onError()
有且只有一個,并且是事件序列中的最后一個。需要注意的是,onCompleted()
和 onError()
二者也是互斥的,即在隊列中調用了其中一個,就不應該再調用另一個,在自己創建的Observable里面應該注意控制。
onError
是當事件隊列異常。在事件處理過程中出異常時,onError()
會被觸發,同時隊列自動終止,不允許再有事件發出。
onNext
,相當于監聽按鈕點擊事件里面的onClick
,即接收到被觀察者發出的事件時,觀察者做處理的函數。
其實還有一個onStart()
方法,不過這個是Subscriber中新添加的,Observer里面是沒有這個方法的。在 subscribe
剛開始,而事件還未發送之前被調用,可以用于做一些準備工作,例如數據的清零或重置。這是一個可選方法,默認情況下它的實現為空。需要注意的是,如果對準備工作的線程有要求(例如彈出一個顯示進度的對話框,這必須在主線程執行), onStart()
就不適用了,因為它總是在 subscribe
所發生的線程被調用,而不能指定線程。要在指定的線程來做準備工作,可以使用 doOnSubscribe()
方法
訂閱
//被觀察者,主動去訂閱觀察者,為了實現鏈式調用就設計成觀察者主動訂閱觀察者。
observeable.subscribe(subscriber);
這樣就完成了一個事件的訂閱的流程。
線程控制Scheduler
在RxJava中我們可以通過Scheduler
來實現多線程,指定事件發送的線程,以及事件處理的線程。事件發送就是Observable
的call方法執行的代碼,通過subscribeOn
我們可以指定call
方法執行的線程。事件處理即Subscriber
事件處理方法執行的線程,通過observerOn可以指定事件處理的線程。不過調用subscribeOn
或 observerOn
切換線程,沒有再做切換,則此次事件一直處于最后一次切換的線程。
需要注意的是,①observerOn
可以多次調用并且有效,但SchedulerOn 雖然也可以多次調用,但是只有第一次是有效的。②默認的Observable
的產生事件,以及通知事件給Observer
是在同一個線程里面的,這個線程就是subscribe
所在的線程。③有些操作符是有默認的調度器的,可以查看官方文檔。比如timer
默認就是computation Scheduler
.
RxJava內置Scheduler
-
Schedulers.immediate()
: 直接在當前線程運行,相當于不指定線程。這是默認的 Scheduler。 -
Schedulers.newThread()
: 總是啟用新線程,并在新線程執行操作。 -
Schedulers.io()
: I/O 操作(讀寫文件、讀寫數據庫、網絡信息交互等)所使用的 Scheduler。行為模式和 newThread() 差不多,區別在于 io() 的內部實現是是用一個無數量上限的線程池,可以重用空閑的線程,因此多數情況下 io() 比 newThread() 更有效率。不要把計算工作放在 io() 中,可以避免創建不必要的線程。 -
Schedulers.computation()
: 計算所使用的 Scheduler。這個計算指的是 CPU 密集型計算,即不會被 I/O 等操作限制性能的操作,例如圖形的計算。這個 Scheduler 使用的固定的線程池,大小為 CPU 核數。不要把 I/O 操作放在 computation() 中,否則 I/O 操作的等待時間會浪費 CPU。 -
AndroidSchedulers.mainThread()
,它指定的操作將在 Android 主線程運行,如果希望Observer
的事件處理發生在主線程的話,就要調用observerOn(AndroidSchedulers.mainThread()).
常用的幾個操作符
-
創建型操作符
from
github介紹:
from() — convert an Iterable, a Future, or an Array into an Observable
from(java.lang.Iterable<? extends T> iterable) /from(T[] array),Observable
可用于將傳入的數組或者集合中拆分成具體的對象,分發下去。
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(0);
list.add(4);
Observable.from(list); //觀察者依次接收到 1,2,0,4.
just
just() — convert an object or several objects into an Observable that emits that object or those objects
just(T t1, T t2··· T tn),其實just與from 差不多,也是將t1, t2··· tn依次分發下去。
Observable.just("Tom" , "John", "Mary");
timer
timer() — create an Observable that emits a single item after a given delay
常用的timer(long delay, java.util.concurrent.TimeUnit unit)
, delay
延遲的時間,unit
延遲的時間的單位。用于定時在一定時間之后,再分發事件,觀察者接收到信號,做相應處理。相當于Handler里面的延遲發送。
Observable.timer(1000, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.io()).unsubscribeOn(Schedulers.io())
.subscribe(new Subscriber<Long>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Long aLong) {
mThread = Thread.currentThread();
Log.i(TAG, "name2" +mThread.getName() + "pid" + mThread.getId());
}
});
interval
interval( ) — create an Observable that emits a sequence of integers spaced by a given time interval
interval(long interval, java.util.concurrent.TimeUnit unit)
,相當于Timer定時器,定時分發事件。與timer
的區別在于可以定時的重復分發事件。而timer只操作一次。repeatWhen
跟interval的作用是一樣的。
- 過濾型操作符
filter
filter() — filter items emitted by an Observable
可以過濾事件,只有符合條件的事件才能被繼續分發下去。
Observable.just(1,2,0,3).filter(new Func1<Integer, Boolean>() {
@Override
public Boolean call(Integer integer) {
//過濾掉等于0的事件
if(integer != 0) {
return integer;
}
return false;
}
})
last
last() — emit only the last item emitted by an Observable
只能發整個事件序列的最后一個事件。同理,first(),只分發整個事件序列的第一個事件。
-
強大的轉換操作符
RxJava
中最強大的就是轉換型操作符了,可以對Observable進行一些轉換,做更多的操作,實現嵌套之類的邏輯。在我看來轉換操作符的作用就是將一個Observable
轉換成另外一個Observable
,我們這里假設只有兩個Observable
,當訂閱之后,第一個Observable
的call方法首先被調用,即第一個Observable
發送事件序列,第二個Observable
對第一個Observable發送出來的事件做處理(比如filiter對事件進行過濾)或者對第一個Observable
返回的數據類型進行處理轉換成另外一個對象或者Observable
(比如Map以及flatMap)然后第二個Observable
開始發送事件序列,最后在Subscribe
里面進行處理。在官方的API里面有形象的"彈珠圖"來演示事件發送的發展。可以點這里去看 ===> RxJava API
map
map
將一個對象轉換成另外一個對象分發下去,返回的是一個Observable對象。
Observable.just(1,2,3,4).map(new Func1<Integer, String>() {
@Override
public String call(Integer i) {
Log.i(TAG, "integer" +i);
return s;
}
});
上述代碼就是將Integer轉換成String對象分發下去。
flatMap
flatMap
可以網絡請求的嵌套,比如請求服務器要先申請到挑戰字 再登錄服務器,代碼就可以如下使用:
InnerServerApi.requestLoginCode(mLoginCodeUrl, getSN()).subscribeOn(Schedulers.io()).observeOn(Schedulers.io())
.flatMap(new Func1<LoginCode, Observable<AAAData>>() {
@Override
public Observable<AAAData> call(LoginCode loginCode) {
Log.d(TAG, loginCode.getLoginCode());
String displayId = android.os.Build.DISPLAY;
return InnerServerApi.requestLoginData(mAAALoginUrl,loginCode.getLoginCode(), "doris", mPassword);
}
// if error, retry ,3 times.
}).subscribeOn(Schedulers.io()).retry(3).subscribe(new Subscriber<AAAData>() {
@Override
public void onCompleted() {
Logger.i(TAG, "onCompleted");
}
@Override
public void onError(Throwable e) {
Log.e(TAG, e.toString());
Log.i(TAG, "login error");
}
@Override
public void onNext(AAAData loginData) {
Log.i(TAG, "login Success");
}
});
map
與 flatMap
的區別在于map
是一對一,而且map是直接返回一個對象,而flatMap
則是返回Observable
,用于分發事件。flatMap
的一對多的體現看下面這段代碼:
Observable.from(folders)
.flatMap(new Func1<File, Observable<File>>() {
@Override
public Observable<File> call(File file) {
return Observable.from(file.listFiles());
}
})
這段代碼,先通過第一個Observable
把一個個目錄發送出去,然后通過flatMap
再把目錄中的文件一個個分發下去。這是map做不到的。
compose
對Observable
進行一個整體的變化,flatMap
只是對接收到的事件一個一個的轉換,而compose
是對整個Observable
做一些處理。
//自定義一個轉換器
final Observable.Transformer schedulersTransformer = new Observable.Transformer() {
@Override
public Object call(Object observable) {
return ((Observable) observable).subscribeOn(Schedulers.io()).unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
};
//這樣就對observable進行了上面call方法里面的操作。
observable.compose(schedulersTransformer);
- 還有很多
zipWith
,merge
等這些合并Observable
,可以看這里Combing操作符
-
錯誤處理操作符
retry
retry(long count)
常用的retry方法,當發生錯誤時,retry count次,比如我們網絡請求的時候失敗了,我們可以重新請求三次,retry(3).
onErrorResumeNext
當發生錯誤的時候,調用這個操作符里面的方法,將當前的Observable
轉換成另一個Observable
繼續發送事件。應用場景,當我們請求當前服務器失敗的時候,可以選擇一個備用的服務器地址重新請求數據。
RxJava 與 Retrofit 結合實例
Retrofit
與RxJava
結合可以更好的完成網絡請求。不了解Retrofit
的,趕緊去學習吧,Retrofit
的底層使用OkHttp
完成網絡請求。下面給大家講講,我用到的網絡請求的例子以及踩過的坑。
-
從服務器獲取文件,失敗的話重試
/** downloadFile是返回的是一個Observable對象,picUrl是要下載的圖片url,我將這個事件發送在IO線程中執行。
retry(3),如果失敗的話,重試三次,下載文件
map 將得到的數據寫入到文件中。
**/
downloadFile(picurl).subscribeOn(Schedulers.io()).unsubscribeOn(Schedulers.io()).retry(3).filter(new Func1<String, Boolean>() {
@Override
public Boolean call(String picUrl) {
//調用filter,過濾掉null的Url,如果picUrl是空的話,那么就不下載
return !TextUtils.isEmpty(picUrl) && !picUrl.equals("null");
}
}).map(new Func1<ResponseBody, String>() {
@Override
public String call(ResponseBody responseBody) {
return writeToFile(responseBody, mFileName);
}
}).subscribe(new Subscriber<String>() {
@Override
public void onCompleted() {
Logger.i(TAG, "testAAA downloadCompleted");
}
@Override
public void onError(Throwable e) {
Logger.i(TAG, "testAAA downloadFail" + e.toString());
}
@Override
public void onNext(String s) {
//返回 下載的文件存放路徑
Logger.i(TAG, "onNext" + s);
}
});
在上述代碼中,通過filter
過濾掉了不合理的url
,其實也可以添加一個判斷是否是一個Http網址的判斷,你可以自己試試。并且通過map
直接將下載到的數據,存儲到了文件當中,而且如果下載過程中出現類似的 socket timeout
錯誤,可以通過,retry
重新請求,一個簡單的鏈式調用就下載文件并保存的邏輯寫好了。 So easy! 必須給RxJava
怒贊!!!
-
嵌套網絡請求
InnerServerApi.requestLoginCode(mLoginCodeUrl, mAccount).subscribeOn(Schedulers.io()).observeOn(Schedulers.io())
.flatMap(new Func1<LoginCode, Observable<AAAData>>() {
@Override
public Observable<AAAData> call(final LoginCode loginCode) {
Log.d(TAG, loginCode.getLoginCode() + "HAHAHA");
return InnerServerApi.requestLoginData(mAAALoginUrl, loginCode.getLoginCode(), mAccount, mPassword);
}
}).subscribeOn(Schedulers.io()).subscribe(new Subscriber<AAAData>() {
@Override
public void onCompleted() {
Logger.i(TAG, "onCompleted");
}
@Override
public void onError(Throwable e) {
Log.d(TAG, "login:onFailure");
}
@Override
public void onNext(AAAData loginData) {
Log.d(TAG, "login:onSuccess:");
}
});
解釋一下,requestLoginCode
是通過Retrofit
定義的一個返回Observable
對象的一個方法,獲取我要登錄服務器需要用到的 驗證字段, 得到驗證字段之后,通過flatMap
直接通過得到的LoginCode
調用登錄方法requestLoginData
,requestLoginData
返回的也是一個Observable對象,然后訂閱,最后獲取到登錄返回的數據。這樣說可能大家不明白,我說說我這個代碼的應用場景,你要登錄一個服務器,首先你要向他申請一個跟你的賬號相關的 驗證碼,然后再通過驗證碼,以及賬號密碼登錄。 就想你要吃蘋果,那么你要先買個水果刀,然后才能切水果最后吃到水果。 大家可以回想一下,沒有RxJava
之前,利用AsyncTask
我們都是如何完成這個邏輯的,首先獲取到LoginCode,然后再Callback的onSuccess
方法中再調用一個AsyncTask
請求,然后就出現了很多迷之縮進,相比之下,RxJava
簡直是飛流直下,邏輯明了清晰。不相信的機智的你,可以寫一段對比一下。 我這里就不貼出來的。
-
錯誤處理 onErrorResumeNext
在應用開發中,會遇到這樣的需求,有幾個備份的網絡地址,當你請求第一個網絡地址不成功的時候,你想要用備份的網絡地址。RxJava
可以很方便的,幫你捕捉到錯誤,并且用備份的地址,重新請求。 就獲取網絡時間的代碼,來舉個例子吧。
Observable.create(new Observable.OnSubscribe<Date>() {
@Override
public void call(Subscriber<? super Date> subscriber) {
try {
//第一次從百度上面獲取網絡時間
mTimeUrl = "http://www.baidu.com/";
Logger.d(TAG, "mTimeUrl" + mTimeUrl);
Date date = GetNetworkTime.getWebsiteDate();
Logger.d(TAG, "call date" + date);
subscriber.onNext(date);
subscriber.onCompleted();
} catch (IOException e) {
//如果網絡超時則調用onError,觸發onErrorResumeNext通過備份的地址 http://www.beijing-time.org 獲取網絡時間。
subscriber.onError(e);
e.printStackTrace();
}
}
}).subscribeOn(Schedulers.io()).unsubscribeOn(Schedulers.io()).onErrorResumeNext(new Func1<Throwable, Observable<? extends Date>>() {
@Override
public Observable<? extends Date> call(Throwable throwable) {
return Observable.create(new Observable.OnSubscribe<Date>() {
@Override
public void call(Subscriber<? super Date> subscriber) {
//通過http://www.beijing-time.org 請求時間,如果這次訪問失敗,則獲取網絡時間失敗
mTimeUrl = "http://www.beijing-time.org";
try {
Date date = GetNetworkTime.getWebsiteDate();
Logger.d(TAG, "call1" + date);
subscriber.onNext(date);
subscriber.onCompleted();
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
});
}
}).subscribe(new Observer<Date>() {
@Override
public void onCompleted() {
Logger.d(TAG, "onCompleted");
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Date date) {
Logger.d(TAG, "call3" + date);
netWorkCallback.doSometing(date);
}
});
在上述代碼中,當第一個Observable
發送獲取網絡時間的時候超時的時候,我們自己調用OnError
方法,從而onErrorResumeNext
攔截到錯誤并且將當前的Observable
轉換成另外一個Observable
,通過這個新的Observable
繼續發送新的事件,這里的新的事件就是通過備份的url,再次獲取網絡是事件。這個就可以完成多個地址切換請求網絡數據,當第一個地址不成功,換第二個地址。這在做應用開發時也經常用到。我覺得挺實用的,當然onErrorResumeNext
的應用場景還有很多,就等你慢慢發掘吧。
總結
在用RxJava的時候,要注意當前的操作是哪個線程。注意不要把應該在UI線程操作的放在了子線程,也不要把大量的操作放在主線程。
在
Observable
發送一串事件序列的時候,如果其中有一個出錯了,那么接下來的事件,觀察者都不會接收到。即OnError
是整個事件結束的標志,如果出錯了,并且沒有做什么出錯處理,那么就直接調用了OnError
,結束整個事件。 所以這意味著,你利用RxJava
發送了一個下載十個文件的事件序列時,如果其中有一個文件下載失敗,其他的文件就停止下載了。這個時候你只能用,for循環,然后一個一個下載,方便控制。
參考鏈接
最后的最后,我說的哪里不對的或者有什么問題,歡迎留言,大家共進步。O