Android RxJava+Retrofit完美封裝(快取,請求,生命週期管理)
*文章已授權微信公眾號 guolin_blog (郭霖)獨家釋出
拖拖踏踏的第三篇文章,我又來造輪子了,一直糾結要不要寫這個主題的文章,總感覺的自己駕馭不了RxJava這麼高深的東西。本篇可能比較多的是個人的理解。
------------- 2018-05-21更新--------------
升級為Retrofit2.0+RxJava2
的版本,專案結構做了一些修改。專案地址https://github.com/Hemumu/Template
前言
Retrofit
和RxJava
已經出來很久了,很多前輩寫了很多不錯的文章,在此不得不感謝這些前輩無私奉獻的開源精神,能讓我們站在巨人的肩膀上望得更遠。對於 RxJava
不是很瞭解的同學推薦你們看扔物線大神的這篇文章給 Android 開發者的 RxJava 詳解一遍看不懂就看第二遍。Retrofit
的使用可以參考Android Retrofit 2.0使用
本文內容是基於Retrofit + RxJava
做的一些巧妙的封裝。參考了很多文章加入了一些自己的理解,請多指教。原始碼地址https://github.com/Hemumu/RxSample
先放出build.gradle
compile 'io.reactivex:rxjava:1.1.0'
compile 'io.reactivex:rxandroid:1.1.0'
compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4'
compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4'
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0-beta4'
本文是基於RxJava1.1.0
和Retrofit 2.0.0-beta4
來進行的。
初始化 Retrofit
新建類Api
,此類就是初始化Retrofit
,提供一個靜態方法初始化Retrofit
非常簡單.
private static ApiService SERVICE;
/**
* 請求超時時間
*/
private static final int DEFAULT_TIMEOUT = 10000;
public static ApiService getDefault() {
if (SERVICE == null) {
//手動建立一個OkHttpClient並設定超時時間
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
httpClientBuilder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
/**
* 對所有請求新增請求頭
*/
httpClientBuilder.addInterceptor(new Interceptor() {
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
okhttp3.Response originalResponse = chain.proceed(request);
return originalResponse.newBuilder().header("key1", "value1").addHeader("key2", "value2").build();
}
});
SERVICE = new Retrofit.Builder()
.client(httpClientBuilder.build())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl(Url.BASE_URL)
.build().create(ApiService.class);
}
return SERVICE;
}
提供一個靜態方法初始化Retrofit
,手動建立了OkHttpClient
設定了請求的超時時間。並在OkHttp的攔截器中增加了請求頭。注意這裡是為所有的請求新增了請求頭,你可以單獨的給請求增加請求頭,例如
@Headers("apikey:b86c2269fe6588bbe3b41924bb2f2da2")
@GET("/student/login")
Observable<HttpResult> login(@Query("phone") String phone, @Query("password") String psw);
和Retrofit
初始化不同的地方就在我們新增了這兩句話
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
service的定義也從這樣
@GET("/student/login")
Call<HttpResult> getTopMovie(@Query("start") int start, @Query("count") int count);
變成了
@GET("/student/login")
Observable<HttpResult> login(@Query("phone") String phone, @Query("password") String psw);
返回值變成了Observable
,這個Observable
不就是RxJava的可觀察者(即被觀察者)麼。
封裝伺服器請求以及返回資料
使用者在使用任何一個網路框架都只關係請求的返回和錯誤資訊,所以對請求的返回和請求要做一個細緻的封裝。
我們一般請求的返回都是像下面這樣
{
"code":"200",
"message":"Return Successd!",
"data":{
"name":"張三"
"age":3
}
}
如果你們的伺服器返回不是這樣的格式那你就只有坐下來請他喝茶,跟他好好說(把他頭摁進顯示器)了。大不了就獻出你的菊花吧!
對於這樣的資料我們肯定要對code
做出一些判斷,不同的code對應不同的錯誤資訊。所以我們新建一個HttpResult
類,對應上面的資料結構。
public class HttpResult<T> {
private int code;
private String message;
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
這算是所有實體的一個基類,data可以為任何資料型別。
我們要對所以返回結果進行預處理,新建一個RxHelper
,預處理無非就是對code
進行判斷和解析,不同的錯誤返回不同的錯誤資訊,這還不簡單。Rxjava
的map
操作符不是輕鬆解決
Api.getDefault().login("name","psw")
.map(new HttpResultFunc<UserEntity>());
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
private class HttpResultFunc<T> implements Func1<HttpResult<T>, T> {
@Override
public T call(HttpResult<T> httpResult) {
Log.e("error", httpResult.getData().toString() + "");
if (httpResult.getCode() != 0) {
throw new ApiException(httpResult.getCode());
}
return httpResult.getData();
}
}
喲,這不是輕鬆愉快 so seay麼!對code進行了判斷,code為0就做對應更新UI或者其他後續操作,不等於0就丟擲異常,在ApiException
中隊code
做處理,根據message欄位進行提示使用者
private static String getApiExceptionMessage(int code){
switch (code) {
case USER_NOT_EXIST:
message = "該使用者不存在";
break;
case WRONG_PASSWORD:
message = "密碼錯誤";
break;
default:
message = "未知錯誤";
}
return message;
}
撒花!!!
然而。。。RxJava
永遠比你想象的強大。RxJava
中那麼多操作符看到我身體不適,有個操作符compose
。因為我們在每一個請求中都會處理code
以及一些重用一些操作符,比如用observeOn
和subscribeOn
來切換執行緒。RxJava提供了一種解決方案:Transformer(轉換器),一般情況下就是通過使用操作符Observable.compose()來實現。具體可以參考避免打斷鏈式結構:使用.compose( )操作符
新建一個RxHelper
對結果進行預處理,程式碼
public class RxHelper {
/**
* 對結果進行預處理
*
* @param <T>
* @return
*/
public static <T> Observable.Transformer<HttpResult<T>, T> handleResult() {
return new Observable.Transformer<HttpResult<T>, T>() {
@Override
public Observable<T> call(Observable<HttpResult<T>> tObservable) {
return tObservable.flatMap(new Func1<HttpResult<T>, Observable<T>>() {
@Override
public Observable<T> call(HttpResult<T> result) {
LogUtils.e(result.getCode()+"");
if (result.getCode() == 0) {
return createData(result.getData());
} else {
return Observable.error(new ApiException(result.getCode()));
}
}
}).subscribeOn(Schedulers.io()).unsubscribeOn(Schedulers.io()).subscribeOn(AndroidSchedulers.mainThread()).observeOn(AndroidSchedulers.mainThread());
}
};
}
/**
* 建立成功的資料
*
* @param data
* @param <T>
* @return
*/
private static <T> Observable<T> createData(final T data) {
return Observable.create(new Observable.OnSubscribe<T>() {
@Override
public void call(Subscriber<? super T> subscriber) {
try {
subscriber.onNext(data);
subscriber.onCompleted();
} catch (Exception e) {
subscriber.onError(e);
}
}
});
}
}
Transformer
實際上就是一個Func1<Observable<T>, Observable<R>>
,換言之就是:可以通過它將一種型別的Observable
轉換成另一種型別的Observable
,和呼叫一系列的內聯操作符是一模一樣的。這裡我們首先使用flatMap
操作符把Obserable<HttpResult<T>>
,轉換成為Observable<T>
在內部對code進行了預處理。如果成功則把結果Observable<T>
發射給訂閱者。反之則把code交給ApiException
並返回一個異常,ApiException
中我們對code
進行相應的處理並返回對應的錯誤資訊
public class ApiException extends RuntimeException{
public static final int USER_NOT_EXIST = 100;
public static final int WRONG_PASSWORD = 101;
private static String message;
public ApiException(int resultCode) {
this(getApiExceptionMessage(resultCode));
}
public ApiException(String detailMessage) {
super(detailMessage);
}
@Override
public String getMessage() {
return message;
}
/**
* 由於伺服器傳遞過來的錯誤資訊直接給使用者看的話,使用者未必能夠理解
* 需要根據錯誤碼對錯誤資訊進行一個轉換,在顯示給使用者
* @param code
* @return
*/
private static String getApiExceptionMessage(int code){
switch (code) {
case USER_NOT_EXIST:
message = "該使用者不存在";
break;
case WRONG_PASSWORD:
message = "密碼錯誤";
break;
default:
message = "未知錯誤";
}
return message;
}
}
最後呼叫了頻繁使用的subscribeOn()
和observeOn()
以及unsubscribeOn()
。
處理ProgressDialog
在Rxjava
中我們什麼時候來顯示Dialog
呢。一開始覺得是放在Subscriber<T>
的onStart
中。onStart
可以用作流程開始前的初始化。然而 onStart()
由於在 subscribe()
發生時就被呼叫了,因此不能指定執行緒,而是隻能執行在 subscribe()
被呼叫時的執行緒。所以onStart
並不能保證永遠在主執行緒執行。
怎麼辦呢?
千萬不要小看了RxJava
,與 onStart()
相對應的有一個方法 doOnSubscribe()
,它和 onStart()
同樣是在subscribe()
呼叫後而且在事件傳送前執行,但區別在於它可以指定執行緒。預設情況下, doOnSubscribe()
執行在 subscribe()
發生的執行緒;而如果在 doOnSubscribe()
之後有 subscribeOn()
的話,它將執行在離它最近的subscribeOn()
所指定的執行緒。可以看到在RxHelper
中看到我們呼叫了兩次subscribeOn
,最後一個呼叫也就是離doOnSubscribe()
最近的一次subscribeOn
是指定的AndroidSchedulers.mainThread()
也就是主執行緒。這樣我們就就能保證它永遠都在主線執行了。這裡不得不感概RxJava
的強大。
這裡我們自定義一個類ProgressSubscriber
繼承Subscriber<T>
public abstract class ProgressSubscriber<T> extends Subscriber<T> implements ProgressCancelListener{
private SimpleLoadDialog dialogHandler;
public ProgressSubscriber(Context context) {
dialogHandler = new SimpleLoadDialog(context,this,true);
}
@Override
public void onCompleted() {
dismissProgressDialog();
}
/**
* 顯示Dialog
*/
public void showProgressDialog(){
if (dialogHandler != null) {
dialogHandler.obtainMessage(SimpleLoadDialog.SHOW_PROGRESS_DIALOG).sendToTarget();
}
}
@Override
public void onNext(T t) {
_onNext(t);
}
/**
* 隱藏Dialog
*/
private void dismissProgressDialog(){
if (dialogHandler != null) {
dialogHandler.obtainMessage(SimpleLoadDialog.DISMISS_PROGRESS_DIALOG).sendToTarget();
dialogHandler=null;
}
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
if (false) { //這裡自行替換判斷網路的程式碼
_onError("網路不可用");
} else if (e instanceof ApiException) {
_onError(e.getMessage());
} else {
_onError("請求失敗,請稍後再試...");
}
dismissProgressDialog();
}
@Override
public void onCancelProgress() {
if (!this.isUnsubscribed()) {
this.unsubscribe();
}
}
protected abstract void _onNext(T t);
protected abstract void _onError(String message);
}
初始化ProgressSubscriber
新建了一個我們自己定義的ProgressDialog
並且傳入一個自定義介面ProgressCancelListener
。此介面是在SimpleLoadDialog
消失onCancel
的時候回撥的。用於終止網路請求。
load.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
mProgressCancelListener.onCancelProgress();
}
});
ProgressSubscriber
其他就很簡單了,在onCompleted()
和onError()
的時候取消Dialog。需要的時候呼叫showProgressDialog
即可。
處理資料快取
伺服器返回的資料我們肯定要做快取,所以我們需要一個RetrofitCache
類來做快取處理。
public class RetrofitCache {
/**
* @param cacheKey 快取的Key
* @param fromNetwork
* @param isSave 是否快取
* @param forceRefresh 是否強制重新整理
* @param <T>
* @return
*/
public static <T> Observable<T> load(final String cacheKey,
Observable<T> fromNetwork,
boolean isSave, boolean forceRefresh) {
Observable<T> fromCache = Observable.create(new Observable.OnSubscribe<T>() {
@Override
public void call(Subscriber<? super T> subscriber) {
T cache = (T) Hawk.get(cacheKey);
if (cache != null) {
subscriber.onNext(cache);
} else {
subscriber.onCompleted();
}
}
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
//是否快取
if (isSave) {
/**
* 這裡的fromNetwork 不需要指定Schedule,在handleRequest中已經變換了
*/
fromNetwork = fromNetwork.map(new Func1<T, T>() {
@Override
public T call(T result) {
Hawk.put(cacheKey, result);
return result;
}
});
}
//強制重新整理
if (forceRefresh) {
return fromNetwork;
} else {
return Observable.concat(fromCache, fromNetwork).first();
}
}
}
幾個引數註釋上面已經寫得很清楚了,不需要過多的解釋。這裡我們先取了一個Observable<T>
物件fromCache
,裡面的操作很簡單,去快取裡面找個key對應的快取,如果有就發射資料。在fromNetwork
裡面做的操作僅僅是快取資料這一操作。最後判斷如果強制重新整理就直接返回fromNetwork
反之用Observable.concat()
做一個合併。concat
操作符將多個Observable
結合成一個Observable
併發射資料。這裡又用了first()
。fromCache
和fromNetwork
任何一步一旦發射資料後面的操作都不執行。
最後我們新建一個HttpUtil用來返回使用者關心的資料,快取,顯示Dialog在這裡面進行。
public class HttpUtil{
/**
* 構造方法私有
*/
private HttpUtil() {
}
/**
* 在訪問HttpUtil時建立單例
*/
private static class SingletonHolder {
private static final HttpUtil INSTANCE = new HttpUtil();
}
/**
* 獲取單例
*/
public static HttpUtil getInstance() {
return SingletonHolder.INSTANCE;
}
//新增執行緒管理並訂閱
public void toSubscribe(Observable ob, final ProgressSubscriber subscriber,String cacheKey,boolean isSave, boolean forceRefresh) {
//資料預處理
Observable.Transformer<HttpResult<Object>, Object> result = RxHelper.handleResult();
//重用操作符
Observable observable = ob.compose(result)
.doOnSubscribe(new Action0() {
@Override
public void call() {
//顯示Dialog和一些其他操作
subscriber.showProgressDialog();
}
});
//快取
RetrofitCache.load(cacheKey,observable,isSave,forceRefresh).subscribe(subscriber);
}
Activity生命週期管理
基本的網路請求都是向伺服器請求資料,客戶端拿到資料後更新UI。但也不排除意外情況,比如請求回資料途中Activity
已經不在了,這個時候就應該取消網路請求。
要實現上面的功能其實很簡單,兩部分
- 隨時監聽Activity(Fragment)的生命週期並對外發射出去; 在我們的網路請求中,接收生命週期
- 並進行判斷,如果該生命週期是自己繫結的,如Destory,那麼就斷開資料向下傳遞的過程
實現以上功能需要用到Rxjava
的Subject
的子類PublishSubject
在你的BaseActivity
中新增如下程式碼
public class BaseActivity extends AppCompatActivity {
public final PublishSubject<ActivityLifeCycleEvent> lifecycleSubject = PublishSubject.create();
@Override
protected void onCreate(Bundle savedInstanceState) {
lifecycleSubject.onNext(ActivityLifeCycleEvent.CREATE);
super.onCreate(savedInstanceState);
}
@Override
protected void onPause() {
lifecycleSubject.onNext(ActivityLifeCycleEvent.PAUSE);
super.onPause();
}
@Override
protected void onStop() {
lifecycleSubject.onNext(ActivityLifeCycleEvent.STOP);
super.onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
lifecycleSubject.onNext(ActivityLifeCycleEvent.DESTROY);
}
這樣的話,我們把所有生命週期事件都傳給了PublishSubject
了,或者說PublishSubject
已經接收到了並能夠對外發射各種生命週期事件的能力了。
現在我們要讓網路請求的時候去監聽這個PublishSubject
,在收到相應的生命週期後取消網路請求,這又用到了我們神奇的compose()
,我們需要修改handleResult
程式碼如下
public static <T> Observable.Transformer<HttpResult<T>, T> handleResult(final ActivityLifeCycleEvent event,final PublishSubject<ActivityLifeCycleEvent> lifecycleSubject) {
return new Observable.Transformer<HttpResult<T>, T>() {
@Override
public Observable<T> call(Observable<HttpResult<T>> tObservable) {
Observable<ActivityLifeCycleEvent> compareLifecycleObservable =
lifecycleSubject.takeFirst(new Func1<ActivityLifeCycleEvent, Boolean>() {
@Override
public Boolean call(ActivityLifeCycleEvent activityLifeCycleEvent) {
return activityLifeCycleEvent.equals(event);
}
});
return tObservable.flatMap(new Func1<HttpResult<T>, Observable<T>>() {
@Override
public Observable<T> call(HttpResult<T> result) {
if (result.getCount() != 0) {
return createData(result.getSubjects());
} else {
return Observable.error(new ApiException(result.getCount()));
}
}
}) .takeUntil(compareLifecycleObservable).subscribeOn(Schedulers.io()).unsubscribeOn(Schedulers.io()).subscribeOn(AndroidSchedulers.mainThread()).observeOn(AndroidSchedulers.mainThread());
}
};
}
呼叫的時候增加了兩個引數一個是ActivityLifeCycleEvent
其實就是一些列舉表示Activity
的生命週期
public enum ActivityLifeCycleEvent {
CREATE,
START,
RESUME,
PAUSE,
STOP,
DESTROY
}
另外一個引數就是我們在BaseActivity
新增的PublishSubject
,這裡用到了takeUntil()
它的作用是監聽我們建立的compareLifecycleObservable
,compareLifecycleObservable
中就是判斷了如果當前生命週期和Activity
一樣就發射資料,一旦compareLifecycleObservable 對外發射了資料,就自動把當前的Observable
(也就是網路請求的Observable
)停掉。
當然有個庫是專門針對這種情況的,叫RxLifecycle,不過要繼承他自己的RxActivity
,當然這個庫不只是針對網路請求,其他所有的Rxjava
都可以。有需要的可以去看看。
最後新建一個ApiService
存放我們的請求
public interface ApiService {
@GET("/student/mobileRegister")
Observable<HttpResult<UserEntity>> login(@Query("phone") String phone, @Query("password") String psw);
}
使用
使用起來就超級簡單了
/**
*
*
// ┏┓ ┏┓
//┏┛┻━━━┛┻┓
//┃ ┃
//┃ ━ ┃
//┃ ┳┛ ┗┳ ┃
//┃ ┃
//┃ ┻ ┃
//┃ ┃
//┗━┓ ┏━┛
// ┃ ┃ 神獸保佑
// ┃ ┃ 阿彌陀佛
// ┃ ┗━━━┓
// ┃ ┣┓
// ┃ ┏┛
// ┗┓┓┏━┳┓┏┛
// ┃┫┫ ┃┫┫
// ┗┻┛ ┗┻┛
//
*/
//獲取豆瓣電影TOP 100
Observable ob = Api.getDefault().getTopMovie(0, 100);
HttpUtil.getInstance().toSubscribe(ob, new ProgressSubscriber<List<Subject>>(this) {
@Override
protected void _onError(String message) {
Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show();
}
@Override
protected void _onNext(List<Subject> list) {
}
}, "cacheKey", ActivityLifeCycleEvent.PAUSE, lifecycleSubject, false, false);
具體很多東西都可以在使用的時候具體修改,比如快取我用的Hawk
。Dialog
是我自己定義的一個SimpleLoadDialog
。原始碼已經給出請多指教!
-------------更新--------------
評論區有人提出對於Activity
生命週期的管理,個人疏忽大意,特地來加上。
END!
Thanks
Rx處理伺服器請求、快取的完美封裝
給 Android 開發者的 RxJava 詳解
RxJava 與 Retrofit 結合的最佳實踐
可能是東半球最全的RxJava使用場景小結
帶你學開源專案:RxLifecycle - 當Activity被destory時自動暫停網路請求
相關文章
- Envoy 代理中的請求的生命週期
- Android Activity生命週期Android
- Laravel 請求生命週期Laravel
- 安卓MVP模式下的Rxjava+Retrofit統一異常處理與生命週期管理安卓MVP模式RxJava
- Tomcat生命週期管理Tomcat
- Laravel 請求週期Laravel
- Android之各生命週期Android
- 封裝axios請求封裝iOS
- Salesforce 生命週期管理(一)應用生命週期淺談Salesforce
- Android元件化開發實戰:封裝許可權管理請求框架Android元件化封裝框架
- ElasticSearch-生命週期管理Elasticsearch
- Android Service生命週期淺析Android
- Flutter仿Android生命週期LifecycleStateFlutterAndroid
- Flutter生命週期的獲取Flutter
- 封裝ajax、axios請求封裝iOS
- npm scripts的生命週期管理NPM
- Elasticsearch索引生命週期管理方案Elasticsearch索引
- Android View的生命週期詳解AndroidView
- Android 監聽生命週期工具庫Android
- Android中元件生命週期完全解析Android元件
- Android全面解析之Activity生命週期Android
- Vue生命週期activated之返回上一頁不重新請求資料Vue
- Django(47)drf請求生命週期分析Django
- OC:封裝網路請求封裝
- axios 請求資料封裝iOS封裝
- 使用RxJava快取Rest請求RxJava快取REST
- Spring生命週期管理之總結Spring
- Elasticsearch ILM DSL 索引生命週期管理Elasticsearch索引
- PLM產品生命週期管理展望
- Flutter - 生命週期監聽和管理Flutter
- 最全面的 Bug 生命週期管理
- Android Activity生命週期的一點感悟Android
- Android知識重溫之生命週期Android
- Android生命週期元件Lifecycle使用詳解Android元件
- Android:聊聊 MVP 中 Presenter 的生命週期AndroidMVP
- 關於Laravel的二、三事(1)一次請求的生命週期Laravel
- 一條HTTP請求的生命週期(二)-- TCP, 本文基於 RFC793HTTPTCP
- 生命週期
- vue中axios請求的封裝VueiOS封裝