不同的网络请求有不同的返回结果,当同时也有很多相同的地方,比如数据的整体结构可以是这样:
1 2 3 4 5 6 7 8
| { "code": 1000, "msg": "调用权限失败", "data": { *** *** } }
|
如果接口数据的设计如上,那么每个请求都会有如下三点相同的部分
- 状态码
- 网络异常
- 相同的网络请求策略
既然有相同的部分,那么就有必要对相同的部分统一处理
主要功能图解
整体采用MVP设计模式,如下:
其中ModelPresenter为所有网络请求的Presenter,如下:
DataService为Retrofit请求接口,如下:
网络层的整体流程如下
其中第三层返回的是HttpBean,第二层返回的是业务层需要的T类型
具体实现
模型设计
在和后台对接的时候,定义一个统一的数据结构,这样才好统一处理状态码,利用泛型,我们可以设计借口返回的数据模型为
1 2 3 4 5
| public class HttpBean<T> { private String msg; private T data; private int code; }
|
不同的网络请求只需要传入相应的数据模型即可,那么利用Retrofit请求数据的接口如下
1 2 3 4 5 6 7 8 9 10
| 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); }
|
业务层向模型层请求数据的接口如下
1 2 3 4 5 6 7 8 9 10
| public interface ModelPresenter { Observable<BoxData> loadBoxData(String client_id, String secret, long user_id);
Observable<CommentData> loadCommentData(String client_id, String secret, long object_id);
Observable<TopicData> loadTopic(String client_id, String secret, long id); }
|
通过对比两个接口,可以发现业务层无需关心状态码了,只会拿到Observable而不是Obervable<HttpBean>
ModelPresenterImpl的实现
ModelPresenterImpl继承自BaseModelImpl,本身的实现其实很简单,主要工作就是调用DataService对应的方法,然后过滤状态码,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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操作符过滤状态码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
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方法只处理成功的结果,纯粹专一。
配置状态码过滤器
状态码过滤器一共需要两个类
- 常量说明类
1 2 3 4 5 6 7
| 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 = "调用权限失败"; }
|
- 状态码匹配工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 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中对网络请求结果的状态码进行判断,如果不是标志成功的状态码,那么就抛出一个异常,在异常中利用状态码匹配工具类找到对应错误描述并且返回
1 2 3 4 5 6 7 8
| 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参数进行判处理,代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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( )中选择上传数据请求的异常信息是一个不错的选择。当然服务器的异常也可以后台自己收集,这里只是提供一种策略而已。
应用层调用
做完了发送请求,解析数据,错误处理,最后就是应用层调用了,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Override public 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中实现
而不需要再处理以下内容
- 客户端的创建
- 状态码过滤
- 网络异常过滤
上传的源码使用MVP设计模式的思想,如果想了解如何使用MVP的同学可以下载看看。
【原文链接】