美文网首页面试Android UI第三方功能的实现
RxJava结合Retrofit对网络请求结果的统一处理

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

作者: Little_Mango | 来源:发表于2016-10-09 16:18 被阅读6436次

    不同的网络请求有不同的返回结果,当同时也有很多相同的地方,比如数据的整体结构可以是这样:

    {
        "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);
    }
    
    1. 利用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 = "调用权限失败";
    }
    
    1. 状态码匹配工具类
    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中对网络请求结果的状态码进行判断,如果不是标志成功的状态码,那么就抛出一个异常,在异常中利用状态码匹配工具类找到对应错误描述并且返回

    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. 网络异常过滤

    上传的源码使用MVP设计模式的思想,如果想了解如何使用MVP的同学可以下载看看。

    相关文章

      网友评论

      • 有了标签:FilterSubscriber<T>的属性error是做什么用的,没有看到你用的地方?
        是不是应该throwable.setMessage("超时了");,然后子类就可以直接从throwable中拿到'超时了'
        Little_Mango:@大龄菜鸟程序员 调用死掉了应该是类转换异常吧?其实服务器返回动态json也是挺好解决的,你在build一个retrofit对象的时候,不要给它指定GsonConverterFactory,然后拿到数据自己解析Json就可以。比如:
        @Override
        public void onResponse(Response<ResponseBody> response) {
        if (response.isSuccess()) {
        Gson gson = new Gson();
        ResponseBody repsonseBody = response.body().string();
        if (isEmail()) {
        EmailReport reports = gson.fromJson(responseBody, EmailReport.class);
        } else{
        PhoneReport reports = gson.fromJson(repsonseBody, PhoneReport.class);
        }
        }
        }
        其中的isEmail()方法就用来做判断,看看服务器给你返回的是什么类型,你每一种情况都创建不同的模型,根据服务器返回的结果对号入座即可。如果做服务器接口的兄弟是自己公司的,而且方便改动的话,可以让他在HTTP header中添加一个字段来标志当前json类型,然后你的isEmail方法的实现就是一个判断Header 字段的开关,否则你就根据你返回的json内容,自己实现isEmail的逻辑判断了。如果硬要用你提供的代码这种泛型,那是很麻烦的,因为你在Retrofit.create(Class<T> service)方法中需要传入一个可以让你Service<T>的class确定的对象,这是一个非常麻烦的事情,如果是泛型类获取Class<T> class对象还相对容易,但是你这个Service是一个接口,而常规的获取泛型类的class对象的方法是一个成员方法,如果你要获得一个接口的对象,那么你懂的,需要实现所有接口方法,然后才有了对象,才可以调用成员方法。所以还是推荐Gson自己解析,不去使用提供的GsonConverterFactory 。或者你可以自己写一个实现了Converter.Factory的DynamicJsonConverterFactory,然后设置给Retrofit.Builder().addConverterFactory(DynamicJsonConverterFactory.create())。
        大龄菜鸟程序员:大神,有个问题,如果相同的接口但是返回的类型可能会不同,这种接口怎么定义呢?据说是retrofit规定接口定义必须明确的指定返回数据类型?
        <T>Observable<HttpResult<T>> signForPay(@QueryMap Map<String,Object> params);
        比如我这样定义一个接口,调用直接死掉了,他并没有将T对应成我闯进去的参数
        Little_Mango:@有了标签 谢谢提醒,我的Demo确实忘记引用这个error,是我代码写错了,应该改为
        @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方法中先super.onError( e ),然后下一行代码打印的error字符串就是异常过滤后的错题提示。在这里不能通过throwable抛出异常,因为onError( )的调用者是RxJava的代码,无法处理我们抛出的异常。

      本文标题:RxJava结合Retrofit对网络请求结果的统一处理

      本文链接:https://www.haomeiwen.com/subject/bappyttx.html