RxJava结合Retrofit对网络请求结果的统一处理

JacobChave 8年前
   <p>不同的网络请求有不同的返回结果,当同时也有很多相同的地方,比如数据的整体结构可以是这样:</p>    <pre>  <code class="language-java">{      "status": 1000,       "msg": "调用权限失败",       "data": {              ***              ***      }  }</code></pre>    <p>如果接口数据的设计如上,那么每个请求都会有如下三点相同的部分</p>    <ol>     <li>状态码</li>     <li>网络异常</li>     <li>相同的网络请求策略</li>    </ol>    <p><strong>既然有相同的部分,那么就有必要对相同的部分统一处理</strong></p>    <h3><strong>主要功能图解</strong></h3>    <p>整体采用MVP设计模式如下</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/f186092a8f1ef285b47e31516d2c0e05.png"></p>    <p style="text-align:center">MVP架构</p>    <p>其中ModelPresenter为所有网络请求的Presenter,如下</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/fecda57e17674862e79e20d5797b5d1b.png"></p>    <p style="text-align:center">ModelPresenter</p>    <p>DataSevice为Retrofit请求接口如下</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/9dab221a17559099f79fb4ea6b5a14ee.png"></p>    <p style="text-align:center">DataService</p>    <p>网络层的整体流程如下</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/f9e476863a3cbe40b6782ec4db008b15.png"></p>    <p style="text-align:center">网络层流程</p>    <p>其中第三层返回的是HttpBean<T>,第二层返回的是业务层需要的T类型</p>    <h3><strong>具体实现</strong></h3>    <p><strong>模型设计</strong></p>    <p>在和后台对接的时候,定义一个统一的数据结构,这样才好统一处理状态码,利用泛型,我们可以设计接口返回的数据模型为</p>    <pre>  <code class="language-java">public class HttpBean<T> {      private String msg;      private T data;      private int status;  }</code></pre>    <p>不同的网络请求只需要传入相应的数据模型即可,那么利用retrofit请求数据的接口如下</p>    <pre>  <code class="language-java">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);  }</code></pre>    <p>业务层向模型层请求数据的接口如下</p>    <pre>  <code class="language-java">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);  }</code></pre>    <p>通过对比两个接口,可以发现业务层无需关心状态码了,只会拿到Observable<T>而不是Obervable<HttpBean<T>></p>    <p><strong>ModelPresenterImpl的实现</strong></p>    <p>ModelPresenterImpl继承自BaseModelImpl,本身的实现其实很简单,主要工作就是调用DataService对应的方法,然后过滤状态码,代码如下</p>    <pre>  <code class="language-java">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));      }  }</code></pre>    <p><strong>BaseModelImpl的实现</strong></p>    <p>BaseModelImpl做了以下两点工作</p>    <ol>     <li>创建OkHttpClient、Retrofit、DataService <pre>  <code class="language-java">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);  }</code></pre> </li>     <li>利用Rxjava的map操作符过滤状态码 <pre>  <code class="language-java">/** * 给返回结果去掉状态码等属性,  * 如果是查询出错,则返回状态码对应的描述给用户  * @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();   }  }</code></pre> 此处代码是一个关键点,利用操作符map给请求的数据"去壳",只返回给业务层所需要的模型,如果当前请求的状态码不是成功的标志,那么抛出异常,交给应用层的OnError处理,确保应用层的onNext方法只处理成功的结果,纯粹专一。</li>    </ol>    <p><strong>配置状态码过滤器</strong></p>    <p>状态码过滤器一共需要2个类</p>    <ol>     <li> <p>常量说明类</p> <pre>  <code class="language-java">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 = "调用权限失败";  }</code></pre> </li>     <li>状态码匹配工具类 <pre>  <code class="language-java">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;   }  }</code></pre> 在ModelPresenterImpl中对网络请求结果的状态码进行判断,如果不是标志成功的状态码,那么就抛出一个异常,在异常中利用状态码匹配工具类找到对应错误描述并且返回 <pre>  <code class="language-java">public class ApiException extends RuntimeException {   public ApiException(int status) {       super(getErrorDesc(status));   }   private static String getErrorDesc(int status){       return StatusUtils.judgeStatus(status).desc;   }  }</code></pre> 随着业务的扩展,如出现新的状态码,那么只需要往常量类和匹配工具类增加状态码和错误描述即可,不需要更改网络层其它代码,还可以拓展成将错误码和对应描述信息存储在本地,当成配置文件,那么当产品发布之后,如果后台增加错误码,只需要download新的状态码配置文件即可,不需要发布新版本应用。</li>    </ol>    <p><strong>其它网络错误处理</strong></p>    <p>以上已经实现了网络层的功能,包括发起请求,解析返回结果并且统一过滤状态码,将请求成功的结果返回到Observable.onNext(),将失败结果返回到observable.onError()。</p>    <p>然而网络请求并不是一直稳定的,所以所有网络请求都有可能出现超时、无网络链接或者其它40X,50X错误</p>    <p>所以还需要再做一层错误过滤,在Retrofit中,所有的异常都会抛出,并且最终由Observable的onError接收,所以我们可以自定义一个FilterSubscriber继承自Subscriber,实现onError接口,对传入的throwable参数进行判处理,代码如下</p>    <pre>  <code class="language-java">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();          }      }  }</code></pre>    <p>由于我们提取出异常处理类,在异常处理类的onError( )中统一对所有异常进行处理,所以当一些异常确定是或者疑似是服务器的bug,抑或是未知bug,我们应该及时上报服务器,让服务器收集错误信息,及时修复,所以在onError( )中选择上传数据请求的异常信息是一个不错的选择。当然服务器的异常也可以后台自己收集,这里这是提供一种策略而已。</p>    <p>应用层调用</p>    <p>做完了发送请求,解析数据,错误处理,最后就是应用层调用了,代码如下:</p>    <pre>  <code class="language-java">@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);                  }              });  }</code></pre>    <p>需要注意的是,在onError(Throwable e){ }中第一行代码需要super.onError(e),然后接下去的异常信息的描述是error字符串。</p>    <p>做完以上工作之后,往后如果需要添加新的接口,那么只需要以下几步</p>    <ol>     <li>在requestCons添加新的接口的文件路径</li>     <li>增加相应的bean文件</li>     <li>在DataService中添加新的接口方法</li>     <li>在ModelPresenter添加新的接口方法并且在Impl中实现</li>    </ol>    <p>而不需要再处理以下内容</p>    <ol>     <li>客户端的创建</li>     <li>状态码过滤</li>     <li>网络异常过滤</li>    </ol>    <p> </p>    <p> </p>    <p>来自:http://www.jianshu.com/p/c88ebf1e0ca7</p>    <p> </p>