Retrofit2+Rxjava2聯網的封裝
前言
開發過程中,一般都會對網路框架進行再次封裝,以配置各種引數,並適合自己的編碼風格。各種網路框架比較下來,還是Retrofit2+Rxjava2看著最爽,今天把這個東西整理了一下,發出來,示例給出了一般寫法和MVP的寫法。
Retrofit2+Rxjava2
Retrofit2
和Rxjava2
基礎的東西就不說了,直接進入主題。我們的需求是:
1、儘可能簡潔
2、可控制不同請求的載入框,
3、錯誤統一處理
4、頁面銷燬時取消訂閱
5、可根據不同請求處理不同的異常
先看一下最終的效果:
Api.getDefaultService()
.calendarBean("2017-06-29" )
.map(new RxFunction<Calendar>())
.compose(RxSchedulers.<Calendar>io_main())
.subscribe(new RxObserver<Calendar>(this, TAG, 0, false) {
@Override
public void onSuccess(int whichRequest, Calendar calendar) {
tv_.setText(calendar.getLunar());
}
@Override
public void onError(int whichRequest, Throwable e) {
}
});
這個例子其中就包含了以上5點要求,其中大多是對RxObserver
的處理。RxObserver
實現Observer
介面,在Rxjava2
中作為觀察者,在執行onNext
之前先做一些處理,就能相應地減少在View
層的處理。
public RxObserver(Context context, String key, int whichRequest, boolean isShowDialog) {
this.mContext = context;
this.mKey = key;
this.isShowDialog = isShowDialog;
this.mWhichRequest = whichRequest;
mDialog = new ProgressDialog(context);
mDialog.setTitle("請稍後");
mRxManager = RxManager.getInstance();
}
引數說明:
key
:key
是用來區分不同類中聯網的CompositeDisposable
的,以便在這個類銷燬時,可以取消該類中訂閱關係,建議採用包名+類名作為key
,這個後面會詳細說。
whichRequest
:區分不同的請求,用於多個聯網請求結束後對不同請求的處理。
isShowDialog
:是否顯示載入框,RxObserver
內部例項化了一個載入框,可根據需求設定是否顯示。
RxObserver
實現了Observer
的四個方法 void onSubscribe(@NonNull Disposable d);
,void onNext(@NonNull T t);
,void onError(@NonNull Throwable e);
,void onComplete();
。
@Override
public final void onSubscribe(Disposable d) {
mRxManager.add(mKey, d);
if (isShowDialog) {
mDialog.show();
}
onStart(mWhichRequest);
}
onSubscribe(Disposable d)
方法,相當於Rxjava1
中的onStar()
方法,其中的引數是Disposable
,用於取消該訂閱關係,所以在方法中把它新增進了RxManager
中,以方便取消訂閱。同時也判斷了isShowDialog
了是否顯示載入框,添加了一個方法onStart()
方法,同時把mWhichRequest
傳出去方便在外部回撥。
@Override
public final void onNext(T value) {
onSuccess(mWhichRequest, value);
}
onNext(T value)
方法比較簡單,聯網結果成功返回會執行,引數是結果。在這個方法中寫了抽象方法onSuccess(mWhichRequest, value);
同樣把mWhichRequest
傳出,方便處理。
@Override
public final void onComplete() {
if (mDialog.isShowing()) {
mDialog.dismiss();
}
}
onComplete()
方法是聯網正常返回,聯網過程結束時執行,在該方法中判斷這個載入框的顯示與否。
@Override
public final void onError(Throwable e) {
if (mDialog.isShowing()) {
mDialog.dismiss();
}
if (e instanceof EOFException || e instanceof ConnectException || e instanceof SocketException || e instanceof BindException || e instanceof SocketTimeoutException || e instanceof UnknownHostException) {
Toast.makeText(mContext, "網路異常,請稍後重試!", Toast.LENGTH_SHORT).show();
} else if (e instanceof ApiException) {
onError(mWhichRequest, e);
} else {
Toast.makeText(mContext, "未知錯誤!", Toast.LENGTH_SHORT).show();
}
}
onError(Throwable e)
方法稍微複雜一些,整個過程出現異常是會執行這個方法,這裡不只是聯網的過程,還包括對返回資料處理上的異常,比如json
解析失敗等。如果發生異常就不會再走onComplete()
,所以同樣需要判斷載入框的顯示。下面的是對一些異常的處理,這裡只處理了一些網路方面的異常,可以根據需求新增異常判斷,這裡處理的異常只是出現意外的異常。這裡還自定義了一個異常ApiException
,丟擲這個異常都是業務上的問題,比如空資料,格式不對等等,這個是根據返回的狀態值判斷的,方便統一處理錯誤資訊。
public class RxFunction<T> implements Function<HttpResult<T>, T> {
@Override
public T apply(@NonNull HttpResult<T> httpResult) throws Exception {
int retCode = httpResult.getRetCode();
if (retCode != 200) {
switch (retCode) {
case 21001:
throw new ApiException("查詢的日期格式錯誤,格式:yyyy-MM-dd");
// case 2111:
// throw ........
}
}
return httpResult.getResult();
}
}
這就是這個ApiException
產生的地方,也是這句話map(new RxFunction<T>())
用到的邏輯,這個是在Retrofit2
解析json
後得到HttpResult<T>
,轉化成T
的map
操作符,在轉化過程中根據HttpResult
的狀態值,判斷是返回T
還是丟擲ApiException
異常,這裡可以根據返回狀態值新增不同的錯誤提示資訊,異常會在RxObserver
的onError(Throwable e)
被捕捉,統一傳到View層去處理。
compose(RxSchedulers.<Calendar>io_main())
這句話是切換了一下執行緒,等同於這兩句
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
執行在io
執行緒,表現在安卓主執行緒。RxSchedulers
完整的程式碼:
public class RxSchedulers {
public static <T> ObservableTransformer<T, T> io_main() {
return new ObservableTransformer<T, T>() {
@Override
public ObservableSource<T> apply( Observable<T> upstream) {
return upstream.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
};
}
}
還有一個類RxManager
,這個類是用來管理訂閱的,在RxObserver
中,執行onSubscribe
方法時,把引數Disposable
新增進了RxManager
中,看一下RxManager
對應的add方法:
public void add(String key, Disposable disposable) {
Set<String> keySet = map.keySet();
if (keySet.contains(key)) {
CompositeDisposable compositeDisposable = map.get(key);
compositeDisposable.add(disposable);
} else {
CompositeDisposable compositeDisposable = new CompositeDisposable();
compositeDisposable.add(disposable);
map.put(key,compositeDisposable );
}
}
這裡面,給每一個key
初始化了一個CompositeDisposable
並存入map
,把這個Disposable
加入到對應的CompositeDisposable
中。
RxManager
中還有一個方法clear(String key)
:
public void clear(String key) {
Set<String> keySet = map.keySet();
if (keySet.contains(key)) {
CompositeDisposable compositeDisposable = map.get(key);
compositeDisposable.clear();
map.remove(key);
}
}
根據key
得到對應的CompositeDisposable
,並執行compositeDisposable.clear()
來取消compositeDisposable中所有的訂閱關係。這個RxManager
的clear
方法建議放在BaseActivity
的onDestroy()
方法中,這也是為什麼前面說的建議這個key
採用包名+類名的方式的原因,當這個類銷燬的時候,該類中所有的聯網訂閱關係都會被取消,避免記憶體洩漏。為保證這個map
唯一,RxManager
採用了單利模式:
private static RxManager rxManager;
private Map<String, CompositeDisposable> map;
private RxManager() {
map = new HashMap<>();
}
public static RxManager getInstance() {
if (rxManager == null) {
rxManager = new RxManager();
}
return rxManager;
}
BaseActivity的onDestroy:
@Override
protected void onDestroy() {
super.onDestroy();
RxManager.getInstance().clear(TAG);
}
以上就是封裝的netWork
module
的基本使用。
最能體現Retrofit2+Rxjava2
優勢的自然是MVP
的結構,下面就演示一下MVP
的寫法。
MVP
這裡同樣不討論mvp
的基礎知識,也不討論幾種mvp
哪種寫法更正宗,我覺得只要是程式碼邏輯清楚,耦合性低,都是好的程式碼架構。
這裡為求簡便,Presenter
和Model
沒有寫成介面,直接寫的各自的實現類。先看Model
層:
public class MVPModel {
public Observable<Calendar> getCalendar(String date) {
return Api.getDefaultService().calendarBean(date).map(new RxFunction<Calendar>()).compose(RxSchedulers.<Calendar>io_main());
}
}
Model
切換執行緒,對請求的資料進行解析,並轉化成我們需要的Observable<Calendar>
物件。再看View
層:
public interface MVPView {
void setResult(Calendar calendar);
void onError(int whichRequest ,Throwable t);
void onStartLoading(int whichRequest);
void onEndLoading(int whichRequest);
}
View
需要寫成介面,讓對應的Activity
或者Fragment
去實現,View
中除了setResult
和onError
兩個必要的方法外,又寫了onStartLoading
和onEndLoading
兩個方法,這兩個方法是分別在聯網操作開始和結束時候的回撥,已滿足其他需要在操作開始和結束做的操作,比如不用RxObserver
中的載入框,需要其他載入框的,如如SwipeRefreshLayout
。再看Presenter
:
public class MVPPresenter {
private MVPModel mvpModel;
private MVPView mvpView;
public MVPPresenter(MVPView mvpView) {
this.mvpView = mvpView;
mvpModel = new MVPModel();
}
public void getCalendar(Context context,String date, String key,int whichRequest ,boolean isShowDialog) {
mvpModel.getCalendar(date).subscribe(new RxObserver<Calendar>(context,key,whichRequest,isShowDialog) {
@Override
public void onStart(int whichRequest) {
super.onStart(whichRequest);
mvpView.onStartLoading(whichRequest);
}
@Override
public void onSuccess(int whichRequest, Calendar calendar) {
mvpView.onEndLoading(whichRequest);
mvpView.setResult(calendar);
}
@Override
public void onError(int whichRequest, Throwable e) {
mvpView.onError(whichRequest, e);
mvpView.onEndLoading(whichRequest);
}
});
}
}
Presenter
同時持有Model
和View
的物件,把從Model
得到的資料傳到View
去處理。最後看View
的實現類MVPActivity
:
public class MVPActivity extends BaseActivity implements MVPView{
TextView tv_;
private MVPPresenter mvpPresenter;
@Override
protected int getLayoutId() {
return R.layout.activity_mvp;
}
@Override
protected void initData() {
tv_ = (TextView) findViewById(R.id.tv_);
mvpPresenter = new MVPPresenter(this);
}
public void request(View view) {
mvpPresenter.getCalendar(this, "2018-10-01", TAG, 0, false);
}
@Override
public void setResult(Calendar calendar) {
tv_.setText(calendar.getWeekday());
}
/**
* 如果有多個請求可根據whichRequest處理不同請求的異常
* @param whichRequest
* @param t
*/
@Override
public void onError(int whichRequest, Throwable t) {
}
/**
* 如果不使用RxObserver自帶的Dialog,可以在RxObserver中設定false,
* 在onStartLoading和onEndLoading設定需要其他dialog的顯示和消失,如SwipeRefreshLayout
* @param whichRequest
*/
@Override
public void onStartLoading(int whichRequest) {
}
@Override
public void onEndLoading(int whichRequest) {
}
}
整個Activity
看起來非常清爽,由mvpPresenter
發出getCalendar()
的請求,在setResult
中獲得結果,onError
中處理異常,onStartLoading
和onEndLoading
處理載入框,如果一個頁面需要多個請求,可以給RxObserver
傳入不同的whichRequest
值,根據whichRequest
判斷在對應的方法中做不同的操作。
以上就是封裝的這個Retrofit2+Rxjava2
的全部內容了,例子中對Retrofit
的配置不是很多,如果需要快取等其他配置,可自行新增。