不同的網(wǎng)絡(luò)請(qǐng)求有不同的返回結(jié)果撬腾,當(dāng)同時(shí)也有很多相同的地方耕拷,比如數(shù)據(jù)的整體結(jié)構(gòu)可以是這樣:
{
"status": 1000,
"msg": "調(diào)用權(quán)限失敗",
"data": {
***
***
}
}
如果接口數(shù)據(jù)的設(shè)計(jì)如上舒帮,那么每個(gè)請(qǐng)求都會(huì)有如下三點(diǎn)相同的部分
- 狀態(tài)碼
- 網(wǎng)絡(luò)異常
- 相同的網(wǎng)絡(luò)請(qǐng)求策略
既然有相同的部分儿惫,那么就有必要對(duì)相同的部分統(tǒng)一處理
主要功能圖解
整體采用MVP設(shè)計(jì)模式如下
其中ModelPresenter為所有網(wǎng)絡(luò)請(qǐng)求的Presenter嗦随,如下
DataSevice為Retrofit請(qǐng)求接口如下
網(wǎng)絡(luò)層的整體流程如下
其中第三層返回的是HttpBean<T>列荔,第二層返回的是業(yè)務(wù)層需要的T類型
具體實(shí)現(xiàn)
模型設(shè)計(jì)
在和后臺(tái)對(duì)接的時(shí)候,定義一個(gè)統(tǒng)一的數(shù)據(jù)結(jié)構(gòu)枚尼,這樣才好統(tǒng)一處理狀態(tài)碼贴浙,利用泛型,我們可以設(shè)計(jì)接口返回的數(shù)據(jù)模型為
public class HttpBean<T> {
private String msg;
private T data;
private int status;
}
不同的網(wǎng)絡(luò)請(qǐng)求只需要傳入相應(yīng)的數(shù)據(jù)模型即可署恍,那么利用retrofit請(qǐng)求數(shù)據(jù)的接口如下
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);
}
業(yè)務(wù)層向模型層請(qǐng)求數(shù)據(jù)的接口如下
public interface ModelPresenter {
/** * 下載box數(shù)據(jù)接口 */
Observable<BoxData> loadBoxData(String client_id, String secret, long user_id);
/** * 下載評(píng)論數(shù)據(jù)接口 */
Observable<CommentData> loadCommentData(String client_id, String secret, long object_id);
/** * 下載Topic商品 */
Observable<TopicData> loadTopic(String client_id, String secret, long id);
}
通過對(duì)比兩個(gè)接口崎溃,可以發(fā)現(xiàn)業(yè)務(wù)層無(wú)需關(guān)心狀態(tài)碼了,只會(huì)拿到Observable<T>而不是Obervable<HttpBean<T>>
ModelPresenterImpl的實(shí)現(xiàn)
ModelPresenterImpl繼承自BaseModelImpl,本身的實(shí)現(xiàn)其實(shí)很簡(jiǎn)單锭汛,主要工作就是調(diào)用DataService對(duì)應(yīng)的方法笨奠,然后過濾狀態(tài)碼,代碼如下
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的實(shí)現(xiàn)
BaseModelImpl做了以下兩點(diǎn)工作
- 創(chuàng)建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操作符過濾狀態(tài)碼
/** * 給返回結(jié)果去掉狀態(tài)碼等屬性,
* 如果是查詢出錯(cuò),則返回狀態(tài)碼對(duì)應(yīng)的描述給用戶
* @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();
}
}
此處代碼是一個(gè)關(guān)鍵點(diǎn),利用操作符map給請(qǐng)求的數(shù)據(jù)"去殼"朵逝,只返回給業(yè)務(wù)層所需要的模型蔚袍,如果當(dāng)前請(qǐng)求的狀態(tài)碼不是成功的標(biāo)志,那么拋出異常,交給應(yīng)用層的OnError處理啤咽,確保應(yīng)用層的onNext方法只處理成功的結(jié)果晋辆,純粹專一。
配置狀態(tài)碼過濾器
狀態(tài)碼過濾器一共需要2個(gè)類
- 常量說(shuō)明類
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 = "調(diào)用權(quán)限失敗";
}
- 狀態(tài)碼匹配工具類
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;
}
}
在BaseModelImpl中對(duì)網(wǎng)絡(luò)請(qǐng)求結(jié)果的狀態(tài)碼進(jìn)行判斷宇整,如果不是標(biāo)志成功的狀態(tài)碼瓶佳,那么就拋出一個(gè)異常,在異常中利用狀態(tài)碼匹配工具類找到對(duì)應(yīng)錯(cuò)誤描述并且返回
public class ApiException extends RuntimeException {
public ApiException(int status) {
super(getErrorDesc(status));
}
private static String getErrorDesc(int status){
return StatusUtils.judgeStatus(status).desc;
}
}
隨著業(yè)務(wù)的擴(kuò)展鳞青,如出現(xiàn)新的狀態(tài)碼霸饲,那么只需要往常量類和匹配工具類增加狀態(tài)碼和錯(cuò)誤描述即可,不需要更改網(wǎng)絡(luò)層其它代碼臂拓,還可以拓展成將錯(cuò)誤碼和對(duì)應(yīng)描述信息存儲(chǔ)在本地厚脉,當(dāng)成配置文件,那么當(dāng)產(chǎn)品發(fā)布之后胶惰,如果后臺(tái)增加錯(cuò)誤碼傻工,只需要download新的狀態(tài)碼配置文件即可,不需要發(fā)布新版本應(yīng)用孵滞。
其它網(wǎng)絡(luò)錯(cuò)誤處理
以上已經(jīng)實(shí)現(xiàn)了網(wǎng)絡(luò)層的功能中捆,包括發(fā)起請(qǐng)求,解析返回結(jié)果并且統(tǒng)一過濾狀態(tài)碼剃斧,將請(qǐng)求成功的結(jié)果返回到Observable.onNext()轨香,將失敗結(jié)果返回到observable.onError()忽你。
然而網(wǎng)絡(luò)請(qǐng)求并不是一直穩(wěn)定的幼东,所以所有網(wǎng)絡(luò)請(qǐng)求都有可能出現(xiàn)超時(shí)、無(wú)網(wǎng)絡(luò)鏈接或者其它40X科雳,50X錯(cuò)誤根蟹。
因此還需要再做一層錯(cuò)誤過濾,在Retrofit中糟秘,所有的異常都會(huì)拋出简逮,并且最終由Observable的onError接收,所以我們可以自定義一個(gè)FilterSubscriber繼承自Subscriber尿赚,實(shí)現(xiàn)onError接口散庶,對(duì)傳入的throwable參數(shù)進(jìn)行判處理,代碼如下
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 = "超時(shí)了";
}else if (e instanceof JsonSyntaxException){
error = "Json格式出錯(cuò)了";
//假如導(dǎo)致這個(gè)異常觸發(fā)的原因是服務(wù)器的問題凌净,那么應(yīng)該讓服務(wù)器知道悲龟,所以可以在這里
//選擇上傳原始異常描述信息給服務(wù)器
}else {
error = e.getMessage();
}
}
}
由于我們提取出異常處理類,在異常處理類的onError( )中統(tǒng)一對(duì)所有異常進(jìn)行處理冰寻,所以當(dāng)一些異常確定是或者疑似是服務(wù)器的bug须教,抑或是未知bug,我們應(yīng)該及時(shí)上報(bào)服務(wù)器,讓服務(wù)器收集錯(cuò)誤信息轻腺,及時(shí)修復(fù)乐疆,所以在onError( )中選擇上傳數(shù)據(jù)請(qǐng)求的異常信息是一個(gè)不錯(cuò)的選擇。當(dāng)然服務(wù)器的異常也可以后臺(tái)自己收集贬养,這里只是提供一種策略而已挤土。
應(yīng)用層調(diào)用
做完了發(fā)送請(qǐng)求,解析數(shù)據(jù)误算,錯(cuò)誤處理耕挨,最后就是應(yīng)用層調(diào)用了,代碼如下:
@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添加新的接口的文件路徑
- 增加相應(yīng)的bean文件
- 在DataService中添加新的接口方法
- 在ModelPresenter添加新的接口方法并且在Impl中實(shí)現(xiàn)
而不需要再處理以下內(nèi)容
- 客戶端的創(chuàng)建
- 狀態(tài)碼過濾
- 網(wǎng)絡(luò)異常過濾
上傳的源碼使用MVP設(shè)計(jì)模式的思想翰苫,如果想了解如何使用MVP的同學(xué)可以下載看看。