RxJava結合Retrofit對網絡請求結果的統一處理

JacobChave 8年前發布 | 24K 次閱讀 Retrofit RxJava

不同的網絡請求有不同的返回結果,當同時也有很多相同的地方,比如數據的整體結構可以是這樣:

{
    "status": 1000, 
    "msg": "調用權限失敗", 
    "data": {
            ***
            ***
    }
}

如果接口數據的設計如上,那么每個請求都會有如下三點相同的部分

  1. 狀態碼
  2. 網絡異常
  3. 相同的網絡請求策略

既然有相同的部分,那么就有必要對相同的部分統一處理

主要功能圖解

整體采用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做了以下兩點工作

  1. 創建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);
    }
  2. 利用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();
     }
    }
    此處代碼是一個關鍵點,利用操作符map給請求的數據"去殼",只返回給業務層所需要的模型,如果當前請求的狀態碼不是成功的標志,那么拋出異常,交給應用層的OnError處理,確保應用層的onNext方法只處理成功的結果,純粹專一。

配置狀態碼過濾器

狀態碼過濾器一共需要2個類

  1. 常量說明類

    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 = "調用權限失敗";
    }
  2. 狀態碼匹配工具類
    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;
     }
    }
    在ModelPresenterImpl中對網絡請求結果的狀態碼進行判斷,如果不是標志成功的狀態碼,那么就拋出一個異常,在異常中利用狀態碼匹配工具類找到對應錯誤描述并且返回
    public class ApiException extends RuntimeException {
     public ApiException(int status) {
         super(getErrorDesc(status));
     }
     private static String getErrorDesc(int status){
         return StatusUtils.judgeStatus(status).desc;
     }
    }
    隨著業務的擴展,如出現新的狀態碼,那么只需要往常量類和匹配工具類增加狀態碼和錯誤描述即可,不需要更改網絡層其它代碼,還可以拓展成將錯誤碼和對應描述信息存儲在本地,當成配置文件,那么當產品發布之后,如果后臺增加錯誤碼,只需要download新的狀態碼配置文件即可,不需要發布新版本應用。

其它網絡錯誤處理

以上已經實現了網絡層的功能,包括發起請求,解析返回結果并且統一過濾狀態碼,將請求成功的結果返回到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字符串。

做完以上工作之后,往后如果需要添加新的接口,那么只需要以下幾步

  1. 在requestCons添加新的接口的文件路徑
  2. 增加相應的bean文件
  3. 在DataService中添加新的接口方法
  4. 在ModelPresenter添加新的接口方法并且在Impl中實現

而不需要再處理以下內容

  1. 客戶端的創建
  2. 狀態碼過濾
  3. 網絡異常過濾

 

 

來自:http://www.jianshu.com/p/c88ebf1e0ca7

 

 本文由用戶 JacobChave 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!