作者:不洗碗工作室--fhyPayaso
轉載請標明出處
前言
android當前最主流的網路請求框架莫過於RxJava2+Retrofit2+OkHttp3,之前的專案中也曾經使用過Retrofit進行網路請求,二次封裝後使用非常方便。但為解決一些巢狀網路請求存在的問題,這次的新專案打算在網路請求中使用RxJava,體驗一下響應式程式設計的感覺。
首先強力推薦扔物線的經典部落格 給 Android 開發者的 RxJava 詳解
觀察者模式
既然要使用RxJava,就不得不簡單介紹一下觀察者模式,因為RxJava作為一個工具庫,使用的就是擴充形式的觀察者模式。
-
觀察者模式:簡單來說,觀察者模式就是一個物件A (觀察者) 和一個物件B (被觀察者) 達成了一種 訂閱 關係,當物件B觸發了某個事件時,比如一些資料的變化,就會立刻通知物件A,物件A接到通知後作出反應。這樣做的好處就是物件A不用實時監控物件B的狀態,只需在物件B發生特定事件時再作出響應即可。
-
Android中的觀察者模式:舉一個在開發中最常見的觀察者模式的例子
OnClickListener
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //點選事件發生後的操作 } }); 複製程式碼
在這個例子中
button
是被觀察者,View.OnClickListener
是觀察者,而setOnClickListener()
方法即讓二者達成了一種訂閱關係,這樣的效果就是:當button
觸發了被點選的事件後,會通知觀察者,而OnClickListener
接收到通知後,就會呼叫自身介面中的onClick()
作為對點選事件的響應。
介紹
首先我們來看一下官方在github上對RxJava的介紹:
RxJava is a Java VM implementation of Reactive Extensions: a library for composing asynchronous and event-based programs by using observable sequences.(一個通過使用可觀察序列來組成非同步的、基於事件的程式的庫。)
從介紹中我們可以提取出一個關鍵詞:非同步,但安卓中已經有很多解決非同步操作的方法了,比如Handler
和AsyncTask
等,為什麼還選擇RxJava呢,其實目的就是為了讓程式碼更簡潔,而且它的簡潔是與眾不同的,因為RxJava的使用方式是基於事件流的鏈式呼叫,這就保證了隨著程式複雜性的提高,RxJava依然能保持程式碼的簡潔和優雅。具體的例子將在後面結合Retrofit展示。
因為RxJava是基於一種擴充的觀察者模式,所以也有觀察者、被觀察者、訂閱、事件幾個基本概念,這裡借用大神Carson_Ho部落格中的例子解釋一下概念和基本原理。
角色 | 作用 | 類比 |
---|---|---|
Observable(被觀察者) | 事件的產生者 | 顧客 |
Observer(觀察者) | 事件消費者,接收事件後作出響應 | 廚師 |
Subscribe(訂閱) | 將Observable和Observer連線在一起 | 服務員 |
Event(事件) | Observable通知Observer的載體 | 菜品 |
基本實現
-
依賴新增:開始實踐之前記得在
gradle
中新增依賴(本文釋出時的RxJava2.0的最新版本)。compile "io.reactivex.rxjava2:rxjava:2.1.1" compile 'io.reactivex.rxjava2:rxandroid:2.0.1' 複製程式碼
-
Observable(被觀察者) 的建立:
Observable<String> observable = Observable.create(new ObservableOnSubscribe<String>() { @Override public void subscribe(ObservableEmitter<String> e) throws Exception { e.onNext("事件1"); e.onNext("事件2"); e.onNext("事件3"); e.onComplete(); } }); 複製程式碼
Observable.create()
: 建立一個Observable的最基本方法,也可以通過just()
、from()
等方法來簡化操作。ObservableOnSubscribe<>()
: 一個介面,在複寫的subscribe()裡定義需要傳送的事件。ObservableEmitter
: 這是RxJava2中新推出的類,可以理解為發射器,用於發射資料onNext()
和通知onComplete()/onError()
。
-
Observer(觀察者) 的建立:
Observer<String> observer = new Observer<String>() { @Override public void onSubscribe(Disposable d) { Log.d(TAG, "onSubscribe: 達成訂閱"); } @Override public void onNext(String s) { Log.d(TAG, "onNext: 響應了"+s); } @Override public void onError(Throwable e) { Log.d(TAG, "onError: 執行錯誤"); } @Override public void onComplete() { Log.d(TAG, "onComplete: 執行完成"); } }; 複製程式碼
onNext()
:普通事件,通過重寫進行響應即可。onError()
:錯誤事件,當佇列中事件處理出現異常時,就會呼叫該方法,此後不再有事件發出。onComplete()
:完成事件,當佇列中的事件全部處理完成後觸發。- 在一個正常的序列中,
onError()
和onComplete()
應該只有一個並且處於事件佇列的末尾,而且呼叫了一個就不應該再呼叫另一個。
除了
Observer
之外,RxJava 還內建了一個實現了Observer
的抽象類:Subscriber
,兩者的使用方式基本一樣,主要區別在於Subscriber
中新增了兩個方法,下面引用一下扔物線對這兩個方法的解釋:
1、onStart(): 這是 Subscriber 增加的方法。它會在 subscribe 剛開始,而事件還未傳送之前被呼叫,可以用於做一些準備工作,例如資料的清零或重置。這是一個可選方法,預設情況下它的實現為空。需要注意的是,如果對準備工作的執行緒有要求(例如彈出一個顯示進度的對話方塊,這必須在主執行緒執行), onStart() 就不適用了,因為它總是在 subscribe 所發生的執行緒被呼叫,而不能指定執行緒。要在指定的執行緒來做準備工作,可以使用 doOnSubscribe() 方法。
2、unsubscribe(): 這是 Subscriber 所實現的另一個介面 Subscription 的方法,用於取消訂閱。在這個方法被呼叫後,Subscriber 將不再接收事件。一般在這個方法呼叫前,可以使用 isUnsubscribed() 先判斷一下狀態。 unsubscribe() 這個方法很重要,因為在 subscribe() 之後, Observable 會持有 Subscriber 的引用,這個引用如果不能及時被釋放,將有記憶體洩露的風險。所以最好保持一個原則:要在不再使用的時候儘快在合適的地方(例如 onPause() onStop() 等方法中)呼叫 unsubscribe() 來解除引用關係,以避免記憶體洩露的發生。
-
二者達成 subscribe(訂閱) 關係:
observable.subscribe(observer); 複製程式碼
-
鏈式呼叫
上面的三步過程可以通過一條鏈式結構直接呼叫,使得程式碼變得更簡潔。
Observable.create(new ObservableOnSubscribe<String>() { @Override public void subscribe(ObservableEmitter<String> e) throws Exception { e.onNext("事件1"); e.onNext("事件2"); e.onNext("事件3"); e.onComplete(); } }).subscribe(new Observer<String>() { @Override public void onSubscribe(Disposable d) { Log.d(TAG, "onSubscribe: 達成訂閱"); } @Override public void onNext(String s) { Log.d(TAG, "onNext: 響應了"+s); } @Override public void onError(Throwable e) { Log.d(TAG, "onError: 執行錯誤"); } @Override public void onComplete() { Log.d(TAG, "onComplete: 執行完成"); } }); 複製程式碼
在logcat中可以檢視列印結果
執行緒排程
在RxJava中,我們可以自行指定事件產生和事件消費的執行緒,可以通過RxJava中的Scheduler
來實現。
Scheduler
-
RxJava內建的5個Scheduler
-
Schedulers.immediate()
: 直接在當前執行緒執行,相當於不指定執行緒。這是預設的 Scheduler,但是為了防止被錯誤使用,在RxJava2中已經被移除了。 -
Schedulers.newThread()
: 開啟新執行緒,並在新執行緒執行操作。 -
Schedulers.io()
: I/O 操作(讀寫檔案、讀寫資料庫、網路資訊互動等)所使用的 Scheduler。行為模式和 newThread() 差不多,區別在於 io() 的內部實現是是用一個無數量上限的執行緒池,可以重用空閒的執行緒,因此多數情況下 io() 比 newThread() 更有效率。不要把計算工作放在 io() 中,可以避免建立不必要的執行緒。 -
Schedulers.computation()
: 計算所使用的 Scheduler,例如圖形的計算。這個 Scheduler 使用的固定的執行緒池,大小為 CPU 核數。不要把 I/O 操作放在 computation() 中,否則 I/O 操作的等待時間會浪費 CPU。 -
Schedulers.trampoline()
:主要用於延遲工作任務的執行。當我們想在當前執行緒執行一個任務時,但並不是立即,我們可以用.trampoline()
將它入隊,trampoline將會處理它的佇列並且按序執行佇列中每一個任務。
-
-
Android特有的Scheduler
AndroidSchedulers.mainThread()
:指定的操作將在Android的主執行緒中進行,如UI介面的更新操作。
執行緒的控制
subscribeOn()
:指定事件產生的執行緒,例如subscribeOn(Schedulers.io())
可以指定被觀察者的網路請求、檔案讀寫等操作放置在io執行緒。observeOn()
:指定事件消費的執行緒,例如observeOn(AndroidSchedulers.mainThread())
指定Subscriber
中的方法在主執行緒中執行。
在subscribe()
之前寫上兩句subscribeOn(Scheduler.io())
和observeOn(AndroidSchedulers.mainThread())
的使用方式非常常見,它適用於多數的 <後臺執行緒取資料,主執行緒顯示> 的程式策略。
結合Retrofit
在很多時候RxJava都會結合Retrofit一起使用,而且隨著程式的複雜度提高,RxJava簡潔的優越性就會漸漸得到體現。下面就舉幾個例子來具體感受一下RxJava操作符的神奇。
1、單獨使用Retrofit
-
首先建立Service介面,這裡拿一個登入介面來舉例
public interface LoginService { @POST("login") Call<ApiResponse> login(@Query("phone") String username, @Query("password") String password); } 複製程式碼
其中
ApiResponse
是自己定義的統一格式的返回體 -
構造Retrofit併傳送請求
public void login() { Retrofit retrofit = new Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create()) .baseUrl(Config.APP_SERVER_BASE_URL) .build(); retrofit.create(LoginService.class) .login(phone,password) .enqueue(new Callback<ApiResponse>() { @Override public void onResponse(Call<ApiResponse> call, Response<ApiResponse> response) { //登入成功後的操作 } @Override public void onFailure(Call<ApiResponse> call, Throwable t) { //登入失敗後的操作 } }); } 複製程式碼
2、結合RxJava
-
引入依賴:記得給Retrofit新增對RxJava的適配
compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' 複製程式碼
-
建立Service介面
public interface LoginService { @POST("login") Observable<ApiResponse> rxLogin(@Query("phone") String phone, @Query("password") String password); } 複製程式碼
-
構造Retrofit併傳送請求
public void rxLogin() { Retrofit retrofit = new Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create()) //新增對RxJava的支援 .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .baseUrl(Config.APP_SERVER_BASE_URL) .build(); retrofit.create(LoginService.class) .rxLogin(phone,password) .subscribeOn(Schedulers.io()) //io執行緒獲取網路請求 .observeOn(AndroidSchedulers.mainThread()) //主執行緒進行資料更新 .subscribe(new Observer<ApiResponse>() { @Override public void onSubscribe(Disposable d) { } @Override public void onNext(ApiResponse apiResponse) { //登入成功後的操作 } @Override public void onError(Throwable e) { //登入失敗後的操作 } @Override public void onComplete() { } }); } 複製程式碼
可能大家看了之後會感覺並沒有什麼卵用,不但沒有簡潔,反而增加了程式碼,但老哥們彆著急,這只是最簡單的情況,隨著情況變得複雜,你就會感受到RxJava的強大之處。
3、flatMap()解決巢狀請求
平時在網路請求的過程中,可能會出現這樣的情況,註冊成功之後直接呼叫登入介面,如果不用RxJava,正常的想法肯定是在retrofit的onResponse回撥裡再巢狀一層網路請求,就像這樣:
//註冊相關方法
private void register() {
retrofit.create(RetrofitService.class)
.register(phone,password)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(Call<ApiResponse> call, Response<ApiResponse> response) {
Log.d(TAG, "onResponse: 註冊成功");
//註冊成功開始登入請求
login();
}
@Override
public void onFailure(Call<ApiResponse> call, Throwable t) {
Log.d(TAG, "onFailure: 註冊失敗");
}
});
}
//登入相關方法
private void login() {
retrofit.create(RetrofitService.class)
.login(phone,password)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(Call<ApiResponse> call, Response<ApiResponse> response) {
Log.d(TAG, "onResponse: 登入成功");
}
@Override
public void onFailure(Call<ApiResponse> call, Throwable t) {
Log.d(TAG, "onFailure: 登入失敗");
}
});
}
複製程式碼
實際上這種很low的巢狀網路請求正是我們需要避免的,而在RxJava中,通過flatMap()操作符可以避免這種巢狀,flatMap的作用是對傳入的上一個資料流中的物件進行處理,返回下一級所要的物件的Observable包裝,相當於將二維的巢狀過程線性化了,先舉個最原始的例子:
private void loginAndRegister() {
//獲得被觀察者實體
RetrofitService service = retrofit.create(RetrofitService.class);
Observable<ApiResponse> register = service.register(phone, password);
final Observable<ApiResponse> login = service.login(phone, password);
register.subscribeOn(Schedulers.io()) //(註冊被觀察者)切換到IO執行緒進行註冊網路請求
.observeOn(AndroidSchedulers.mainThread()) //(註冊觀察者)切換到主執行緒 處理註冊網路請求的結果
.doOnEach(new Observer<ApiResponse>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(ApiResponse apiResponse) {
// 對第註冊成功網路請求返回的結果進行操作
Log.d(TAG, "註冊成功");
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
})
//切換到IO執行緒去發起登入網路請求(登入被觀察者經過flatMap變換後也變成了相對於註冊的觀察者,所以用observeOn切換執行緒)
.observeOn(Schedulers.io())
.flatMap(new Function<ApiResponse, ObservableSource<ApiResponse>>() {
@Override
public ObservableSource<ApiResponse> apply(ApiResponse apiResponse) throws Exception {
return login;
}
})
//(登入觀察者)切回主執行緒處理回撥
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<ApiResponse>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(ApiResponse apiResponse) {
Log.d(TAG, "onNext: 登入成功");
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
}
複製程式碼
可能有的老哥覺得,這樣寫雖然採用了鏈式呼叫,沒有了巢狀,但是這程式碼也太長了,而且重寫了很多無用的方法,彆著急,下面來個優雅版的
private void loginAndRegister() {
final RetrofitService service = retrofit.create(RetrofitService.class);
service.register(phone, password)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(new Consumer<ApiResponse>() {
@Override
public void accept(ApiResponse apiResponse) throws Exception {
Log.d(TAG, "註冊成功");
}
})
.observeOn(Schedulers.io())
.flatMap(new Function<ApiResponse, ObservableSource<ApiResponse>>() {
@Override
public ObservableSource<ApiResponse> apply(ApiResponse apiResponse) throws Exception {
return service.login(phone, password);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<ApiResponse>() {
@Override
public void accept(ApiResponse apiResponse) throws Exception {
Log.d(TAG, "登入成功");
}
});
}
複製程式碼
Consumer是簡易版的Observer,他有多重過載,可以自定義你需要處理的資訊,這裡呼叫的是隻接受onNext訊息的方法,他只提供一個回撥介面accept,由於沒有onError和onCompete,無法在接受到onError或者onCompete之後,實現函式回撥,雖然無法回撥,但不代表不接收,他還是會接收到onCompete和onError之後做出預設操作,這裡我還是建議大家自己對Observer再進行一下封裝,使用起來會更方便。
在logcat中可以看到請求成功的列印的日誌