RxJava結合Retrofit對網絡請求結果的統一處理
不同的網絡請求有不同的返回結果,當同時也有很多相同的地方,比如數據的整體結構可以是這樣:
{
"status": 1000,
"msg": "調用權限失敗",
"data": {
***
***
}
}
如果接口數據的設計如上,那么每個請求都會有如下三點相同的部分
- 狀態碼
- 網絡異常
- 相同的網絡請求策略
既然有相同的部分,那么就有必要對相同的部分統一處理
主要功能圖解
整體采用MVP設計模式如下
MVP架構
其中ModelPresenter為所有網絡請求的Presenter,如下
ModelPresenter
DataSevice為Retrofit請求接口如下
DataService
網絡層的整體流程如下
網絡層流程
其中第三層返回的是HttpBean<T>,第二層返回的是業務層需要的T類型
具體實現
模型設計
在和后臺對接的時候,定義一個統一的數據結構,這樣才好統一處理狀態碼,利用泛型,我們可以設計接口返回的數據模型為
public class HttpBean<T> {
private String msg;
private T data;
private int status;
}
不同的網絡請求只需要傳入相應的數據模型即可,那么利用retrofit請求數據的接口如下
public interface DataService {
@GET(RequestCons.MY_BOX)
Observable<HttpBean<BoxData>> getBox(@Query("client_id") String client_id, @Query("client_secret") String secret, @Query("visit_user_id") long user_id);
@GET(RequestCons.COMMENTS_LIST)
Observable<HttpBean<CommentData>> getComments(@Query("client_id") String client_id, @Query("client_secret") String secret, @Query("object_id") long object_id);
@GET(RequestCons.TOPIC)
Observable<HttpBean<TopicData>> getTopic(@Query("client_id") String client_id, @Query("client_secret") String secret, @Query("id") long id);
}
業務層向模型層請求數據的接口如下
public interface ModelPresenter {
/** * 下載box數據接口 */
Observable<BoxData> loadBoxData(String client_id, String secret, long user_id);
/** * 下載評論數據接口 */
Observable<CommentData> loadCommentData(String client_id, String secret, long object_id);
/** * 下載Topic商品 */
Observable<TopicData> loadTopic(String client_id, String secret, long id);
}
通過對比兩個接口,可以發現業務層無需關心狀態碼了,只會拿到Observable<T>而不是Obervable<HttpBean<T>>
ModelPresenterImpl的實現
ModelPresenterImpl繼承自BaseModelImpl,本身的實現其實很簡單,主要工作就是調用DataService對應的方法,然后過濾狀態碼,代碼如下
public class ModelPresenterImpl extends BaseModelImpl implements ModelPresenter {
@Override
public Observable<BoxData> loadBoxData(String client_id, String secret, long user_id) {
return filterStatus(mDataService.getBox(client_id,secret,user_id));
}
@Override
public Observable<CommentData> loadCommentData(String client_id, String secret, long object_id) {
return filterStatus(mDataService.getComments(client_id,secret,object_id));
}
@Override
public Observable<TopicData> loadTopic(String client_id, String secret, long id) {
return filterStatus(mDataService.getTopic(client_id,secret,id));
}
}
BaseModelImpl的實現
BaseModelImpl做了以下兩點工作
- 創建OkHttpClient、Retrofit、DataService
public BaseModelImpl() { this.baseUrl = RequestCons.BASE_URL; OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .build(); mRetrofit = new Retrofit.Builder() .baseUrl(baseUrl) .client(client) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .build(); mDataService = mRetrofit.create(DataService.class); }
- 利用Rxjava的map操作符過濾狀態碼
/** * 給返回結果去掉狀態碼等屬性, * 如果是查詢出錯,則返回狀態碼對應的描述給用戶 * @param observable * @return */ public Observable filterStatus(Observable observable){ return observable.map(new ResultFilter()); } private class ResultFilter<T> implements Func1<HttpBean<T>, T> { @Override public T call(HttpBean<T> tHttpBean) { if (tHttpBean.getStatus() != 1){ throw new ApiException(tHttpBean.getStatus()); } return tHttpBean.getData(); } }
配置狀態碼過濾器
狀態碼過濾器一共需要2個類
-
常量說明類
public class ResponseCons { public static final int STATUS_SUCCESS = 1; public static final String SUCCESS_MSG = "成功"; public static final int STATU_1000 = 1000; public static final String FAILURE_1000 = "調用權限失敗"; }
- 狀態碼匹配工具類
public class StatusUtils { public static class StatusResult{ public int status; public String desc; public boolean isSuccess; } private static StatusResult mStatusResult = new StatusResult(); public static StatusResult judgeStatus(int status) { String desc = ""; boolean isSuccess = false; switch (status) { case ResponseCons.STATUS_SUCCESS: desc = ResponseCons.SUCCESS_MSG; isSuccess = true; break; case ResponseCons.STATU_1000: desc = ResponseCons.FAILURE_1000; break; } mStatusResult.status = status; mStatusResult.desc = desc; mStatusResult.isSuccess = isSuccess; return mStatusResult; } }
public class ApiException extends RuntimeException { public ApiException(int status) { super(getErrorDesc(status)); } private static String getErrorDesc(int status){ return StatusUtils.judgeStatus(status).desc; } }
其它網絡錯誤處理
以上已經實現了網絡層的功能,包括發起請求,解析返回結果并且統一過濾狀態碼,將請求成功的結果返回到Observable.onNext(),將失敗結果返回到observable.onError()。
然而網絡請求并不是一直穩定的,所以所有網絡請求都有可能出現超時、無網絡鏈接或者其它40X,50X錯誤
所以還需要再做一層錯誤過濾,在Retrofit中,所有的異常都會拋出,并且最終由Observable的onError接收,所以我們可以自定義一個FilterSubscriber繼承自Subscriber,實現onError接口,對傳入的throwable參數進行判處理,代碼如下
public abstract class FilterSubscriber<T> extends Subscriber<T> {
public String error;
@Override
public abstract void onCompleted();
@Override
public void onError(Throwable e) {
if (e instanceof TimeoutException || e instanceof SocketTimeoutException
|| e instanceof ConnectException){
error = "超時了";
}else if (e instanceof JsonSyntaxException){
error = "Json格式出錯了";
//假如導致這個異常觸發的原因是服務器的問題,那么應該讓服務器知道,所以可以在這里
//選擇上傳原始異常描述信息給服務器
}else {
error = e.getMessage();
}
}
}
由于我們提取出異常處理類,在異常處理類的onError( )中統一對所有異常進行處理,所以當一些異常確定是或者疑似是服務器的bug,抑或是未知bug,我們應該及時上報服務器,讓服務器收集錯誤信息,及時修復,所以在onError( )中選擇上傳數據請求的異常信息是一個不錯的選擇。當然服務器的異常也可以后臺自己收集,這里這是提供一種策略而已。
應用層調用
做完了發送請求,解析數據,錯誤處理,最后就是應用層調用了,代碼如下:
@Overridepublic void loadTopicSuccess() {
Observable<TopicData> observable = mModelPresenter.loadTopic("bt_app_ios", "9c1e6634ce1c5098e056628cd66a17a5", 1346);
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new FilterSubscriber<TopicData>() {
@Override
public void onCompleted() {
MLog.d("Topic信息下載完畢");
}
@Override
public void onNext(TopicData data) {
mMainView.showSuccess(data);
}
@Override
public void onError(Throwable e) {
super.onError(e);
mMainView.showError(error);
}
});
}
需要注意的是,在onError(Throwable e){ }中第一行代碼需要super.onError(e),然后接下去的異常信息的描述是error字符串。
做完以上工作之后,往后如果需要添加新的接口,那么只需要以下幾步
- 在requestCons添加新的接口的文件路徑
- 增加相應的bean文件
- 在DataService中添加新的接口方法
- 在ModelPresenter添加新的接口方法并且在Impl中實現
而不需要再處理以下內容
- 客戶端的創建
- 狀態碼過濾
- 網絡異常過濾
來自:http://www.jianshu.com/p/c88ebf1e0ca7