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

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

{
    "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的同学可以下载看看。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • 我从去年开始使用 RxJava ,到现在一年多了。今年加入了 Flipboard 后,看到 Flipboard 的...
    Jason_andy阅读 5,451评论 7 62
  • 前言我从去年开始使用 RxJava ,到现在一年多了。今年加入了 Flipboard 后,看到 Flipboard...
    占导zqq阅读 9,158评论 6 151
  • 文章转自:http://gank.io/post/560e15be2dca930e00da1083作者:扔物线在正...
    xpengb阅读 7,017评论 9 73
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,567评论 18 399