Retrofit2 结合 Rxjava 解决返回的 JSON

时间:2022-04-10 17:51:09

Retrofit2RxjavaDemo

简介:Retrofit2 结合 Rxjava 解决返回的 JSON 结果为非 Rest 标准后,返回码为正确下使用 Gson 返回真实的数据,保留了 Rxjava 的链式调用,而不是采用回调的方式,最大可能发挥 Rxjava 的特点。

English Version

please click here to see English version. To explain how to retrieve data from non restful response to get the right data(I don't have much time to finish English version, please wait for more detail coverage).


中文版

效果展示

Retrofit2 结合 Rxjava 解决返回的 JSON Retrofit2 结合 Rxjava 解决返回的 JSON

本文主要介绍了使用 Retrofit2 配合 Rxjava 来处理非 restful 的网络请求结果。一般而言请求的非 REST 结果如下:

{
    "status": 1,    //请求返回状态码
    "msg": "请求成功",    //请求返回状态
    "result": {    //返回结果
        "npage": 1,   //当前页
        "pageSize": 10,   //每页信息条数
        "total": 1,    //查询到的信息条数
        "data": [{
            "phone": "010-62770334;010-62782051",    //电话
            "website": "www.tsinghua.edu.cn",    //官网
            "email": "zsb@mail.tsinghua.edu.cn",    //邮箱
            "address": "北京市海淀区清华大学",    //地址
            "zipcode": "",    //邮编
            "name": "清华大学",    //学校名称
            "img": "http://img.jidichong.com/school/3.png",    //学校 logo 图片
            "parent": "教育部",    //隶属部门
            "type": " 211 985",    //学校类型
            "profile": "",   //简介
            "info": "院士:68 人 博士点:198 个 硕士点:181 个",    //说明
            "city": "北京"   //所在省市
        }]
    }
}

而 Retrofit2 请求的结果一般都分为 Header 和 Body。当然使用使用 Rxjava 后,则可以实现获取这个 result 的结果。当是单个实体的时候,在 onNext 中就直接得到这个结果,如果是一个数组的时候,则是返回 List 这种形式。


使用方法:

  1. 配置对应的外层实体,例如下面。开发中一般都非标准的 REST 都是一个数据(百度开放平台的接口就基本都是这种形式),一个状态码和一个消息。其中 data 的类型是泛型,可以在生成请求的 api 指定实际返回的类型,如果为空的情况可以使用 String。 而 code 和 message 是对应于服务端定义的代码 code 和返回的错误信息。Demo 里有初始值是因为找的接口没有使用这两个字段,但我想模拟这类情形而设置的。如果你的后台后台字段不同,你可以按需要修改这个 HttpResult 的相应字段。

     {
             // code 为返回的状态码, message 为返回的消息, 演示的没有这两个字段,考虑到真实的环境中基本包含就在这里写定值
             private int code = 0;
                 private String message = "OK";
    
             //用来模仿 Data
             @SerializedName(value = "subjects")
             private T data;
     }
    
  2. 同 Retrofit2 一样要定义接口,如下。这里仅仅是有 get 的接口在 demo 里,其它的 Put, Delete, Query 等都是一样的。

    HttpResult里的类型就是指定泛型 data 的具体类型。可以使用 String, JSONObject,定义的实体等等。

    另外有朋友问题访问参数是 JSON 对象怎么办 (Body Paramter JSON Object)?这其实就是将你的参数设置成一个已经定义的实体,我也给出一个项目中的接口。下面的 post 就是这种方式。关于请求的 REST 方式我会在文章后面放出详细的参考,若你不熟悉请参考这些文章。

    
             @GET("top250")
         Observable<HttpResult<List<ContentBean>>> getTopMovie(@Query("start") int start, @Query("count") int count);
    
             @POST("user/login")
         Observable<HttpResult<UserLoginBean>> postLogin(@Body UserLoginRequest request);
    
  3. 请求网络。

    直接调用

                         ServiceFactory.movieApi()
                             .getTopMovie(1, 10)
    

    使用一个默认的.compose(new DefaultTransformer<List<ContentBean>>())可以非常方便地进行转化成了需要的Observable

    另外准备了常用的 subscriber,包含了网络连接的错误处理,例如非 200 状态,另外是服务端(业务)错误的处理,默认是将错误编码和错误信息在控制台和手机上输出。 正确的情况下则必须实现。

    建议使用 Rxlifecycle 防止使用 Rxjava 内存泄露。


错误方面

定义了服务器错误和连接错误等。主要使用了 RxJava 中的onErrorResumeNext,遇到错误后将错误通过ExceptionEngine.handleException(throwable)进行处理。

@Override
    public Observable<T> call(Observable<HttpResult<T>> responseObservable) {
        return responseObservable.map(new Func1<HttpResult<T>, T>() {
            @Override
            public T call(HttpResult<T> httpResult) {
                // 通过对返回码进行业务判断决定是返回错误还是正常取数据
//                if (httpResult.getCode() != 200) throw new RuntimeException(httpResult.getMessage());
                if (httpResult.getCode() != ErrorType.SUCCESS) throw new ServerException(httpResult.getMessage(), httpResult.getCode());
                return httpResult.getData();
            }
        }).onErrorResumeNext(new Func1<Throwable, Observable<? extends T>>() {
            @Override
            public Observable<? extends T> call(Throwable throwable) {
                //ExceptionEngine 为处理异常的驱动器
                throwable.printStackTrace();
                return Observable.error(ExceptionEngine.handleException(throwable));
            }
        });
    }

其中的 ApiException

public class ApiException extends Exception {
    // 异常处理,为速度,不必要设置 getter 和 setter
    public int code;
    public String message;

    public ApiException(Throwable throwable, int code) {
        super(throwable);
        this.code = code;
    }
}

重点在于下面这个处理。你可以再定义自己的业务相关的错误,目前常用的都已经有了,而业相关的错误大部分通过接收的数据直接显示了。若不是直接显示的情况,你完全可以在提供的 subscriber 里去实现。

public class ExceptionEngine {
    //对应 HTTP 的状态码
    private static final int UNAUTHORIZED = 401;
    private static final int FORBIDDEN = 403;
    private static final int NOT_FOUND = 404;
    private static final int REQUEST_TIMEOUT = 408;
    private static final int INTERNAL_SERVER_ERROR = 500;
    private static final int BAD_GATEWAY = 502;
    private static final int SERVICE_UNAVAILABLE = 503;
    private static final int GATEWAY_TIMEOUT = 504;

    public static ApiException handleException(Throwable e){
        ApiException ex;
        if (e instanceof HttpException){             //HTTP 错误
            HttpException httpException = (HttpException) e;
            ex = new ApiException(e, ErrorType.HTTP_ERROR);
            switch(httpException.code()){
                case UNAUTHORIZED:
                    ex.message = "当前请求需要用户验证";
                    break;
                case FORBIDDEN:
                    ex.message = "服务器已经理解请求,但是拒绝执行它";
                    break;
                case NOT_FOUND:
                    ex.message = "服务器异常,请稍后再试";
                    break;
                case REQUEST_TIMEOUT:
                    ex.message = "请求超时";
                    break;
                case GATEWAY_TIMEOUT:
                    ex.message = "作为网关或者代理工作的服务器尝试执行请求时,未能及时从上游服务器(URI 标识出的服务器,例如 HTTP、FTP、LDAP)或者辅助服务器(例如 DNS)收到响应";
                    break;
                case INTERNAL_SERVER_ERROR:
                    ex.message = "服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理";
                    break;
                case BAD_GATEWAY:
                    ex.message = "作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应";
                    break;
                case SERVICE_UNAVAILABLE:
                    ex.message = "由于临时的服务器维护或者过载,服务器当前无法处理请求";
                    break;
                default:
                    ex.message = "网络错误";  //其它均视为网络错误
                    break;
            }
            return ex;
        } else if (e instanceof ServerException){    //服务器返回的错误
            ServerException resultException = (ServerException) e;
            ex = new ApiException(resultException, resultException.code);
            ex.message = resultException.message;
            return ex;
        } else if (e instanceof JsonParseException
                || e instanceof JSONException
                || e instanceof ParseException){
            ex = new ApiException(e, ErrorType.PARSE_ERROR);
            ex.message = "解析错误";            //均视为解析错误
            return ex;
        }else if(e instanceof ConnectException || e instanceof SocketTimeoutException || e instanceof ConnectTimeoutException){
            ex = new ApiException(e, ErrorType.NETWORD_ERROR);
            ex.message = "连接失败";  //均视为网络错误
            return ex;
        }
        else {
            ex = new ApiException(e, ErrorType.UNKNOWN);
            ex.message = "未知错误";          //未知错误
            return ex;
        }
    }

}

Update

2016-10-13
修正了服务端 code 没有处理,返回为错误时认为是 json 实体解析问题。
`if (httpResult.getCode() != ErrorType.SUCCESS || httpResult.getCode() != ErrorType.SUCCESS)`

Thanks