Android使用Rxjava、Retrofit处理json解析异常,只看这一篇就够了

概述

日常开发的时候,避免不了与后台打交道,最常见的就是前端发送请求,后台返回数据,然后将拿到的数据进行展示。现在我们开始模仿一个基本的网络请求,这里使用wanandroid提供的开放api作为请求对象,地址:http://www.wanandroid.com/blog/show/2 ,然后我们选择获取文章列表的一个接口 http://wanandroid.com/article/listproject/0/json

基本请求示例

1、导入Rxjava、Retrofit依赖:

//retrofit2
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.5.0'
//rxjava2
implementation 'io.reactivex.rxjava2:rxjava:2.2.2'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'

2、定义BaseResponse类

wanandroid返回的json数据都有一个基本的格式,即

{
	data:T,
	errorCode: 0,
	errorMsg: ""
}

那么,我们需定义一个BaseResponse类,由于data的数据类型不确定,可能是bean,可能是list,这里使用泛型来表示,简单代码如下:

public class BaseResponse<T> implements Parcelable {

    private int errorCode;
    private String error;
    private T data;

 	// ……忽略各种setter、getter方法

    /**
     * 判断请求是否成功
     * @return bool
     */
    public boolean isSuccess(){
        return getErrorCode() == 0;
    }
}

3、配置Retrofit

根据文档,创建ApiService.class,并定义接口

//接口
public interface ApiService {
    /**
     * 获取首页文章列表
     */
    @GET("/article/listproject/{pageIndex}/json")
    Observable<BaseResponse<ArticleBean>> getArticleList(@Path("pageIndex") int pageIndex);
}

//接口对象
public class ApiServiceImpl {

    private ApiServiceImpl() {
        throw new RuntimeException("you can't new me");
    }
    public static ApiService getInstance() {
        return createApiService.apiService;
    }

    /**
     * Retrofit生成接口对象.
     */
    private static class createApiService {
        /**
         * Retrofit会根据传入的接口类.生成实例对象.
         */
        private static final ApiService apiService = RetrofitClient.getInstance().getApi(ApiService.class);
    }
}

Observable<BaseResponse>的ArticleBean是接口data的具体数据类型,接着我们创建Retrofit

OkHttpClient.Builder builder = new OkHttpClient.Builder()
                //链接超时
                .connectTimeout(90, TimeUnit.SECONDS)
                //读取超时
                .readTimeout(90, TimeUnit.SECONDS)
                //失败自动重连
                .retryOnConnectionFailure(true);
//初始化Retrofit并添加配置
Retrofit retrofit = new Retrofit.Builder()
                .client(builder.build())
                .baseUrl(ApiService.BASE_URL)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .build();
public <T> T getApi(Class<T> clz) {
     return mRetrofit.create(clz);
    }

4、发起请求

ApiServiceImpl.getInstance()
                .getArticleList(pageIndex)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<BaseResponse<ArticleBean>>() {
                    @Override
                    public void onSubscribe(Disposable d) {
                    }

                    @Override
                    public void onNext(BaseResponse<ArticleBean> response) {
                        if (response.isSuccess()){
                            ArticleBean articleBean = response.getData();
                            // do something
                        }
                    }

                    @Override
                    public void onError(Throwable e) {
                    }

                    @Override
                    public void onComplete() {
                    }
                });

OK!一切看起来都是这么美好,不过往往事与愿违。

json解析异常

这里请求成功,后台返回的成功的数据类型为

{
	data:{},
	errorCode: 0,
	errorMsg: ""
}

这是没问题的,但是假设请求失败,后台返回的数据类型为

{
	data:[],
	errorCode: 400,
	errorMsg: "参数错误……"
}

请求失败的时候返回的data是一个数组,甚至一个字符串文本,当gson解析的时候,会抛一个JsonParseException,导致拿不到errorCode与errorMsg。

处理json解析异常

好在办法还是有的,对于失败时候的回调处理,有两种方法:

方法一:

让后台统一规范修改

{
	data:{},
	errorCode: 400,
	errorMsg: "参数错误……"
}

改成无论是成功或者失败,data都返回一个对象或者数组。(当然这个是后台好说话,不打人的情况)
显然,即便后台改了,这个也是治标不治本的方法,万一新写的接口没有遵循该规范,或者是新来的同事不知道该规范,这些都是有可能的。那现在我们来看看第二种方法。

方法二:

不知道各位还记得构建Retrofit的那段代码中,有一句.addConverterFactory(GsonConverterFactory.create())
这个ConverterFactory是支持自定义的,也就是说,我们可以自定义自己的converter,不懂ConverterFactory原理的,请参考该文章 秒懂Retrofit2之GsonConverter

自定义Converter

public class ResponseBodyConverter<T> implements Converter<ResponseBody, T> {

    private final TypeAdapter<BaseResponse<T>> adapter;

    ResponseBodyConverter(Gson gson, TypeToken<T> typeToken) {
        ParameterizedTypeImpl parameterizedType = 
        new ParameterizedTypeImpl(null, BaseResponse.class, typeToken.getType());
        //noinspection unchecked
        adapter = (TypeAdapter<BaseResponse<T>>) gson.getAdapter(TypeToken.get(parameterizedType));
    }

    @Override
    public T convert(@NonNull ResponseBody value) throws IOException {
        String json = value.string();
        //第一次解析
        BaseResponse obj = GsonUtils.GsonToBean(json, BaseResponse.class);
        if (!obj.isSuccess()) {
            //如果是服务端返回的错误码,则抛出自定义异常
            throw new ApiException(obj.getErrorCode(), obj.getError());
        }
        //第二次解析
        BaseResponse<T> result = adapter.fromJson(json);
        value.close();
        return result.getData();
    }
    // 省略部分ParameterizedTypeImpl代码
}

我们首先看convert方法,首先获得ResponseBody 的值,用gson解析,第一次是判断code,如果服务端返回的不是成功的状态码,则抛出自定义的ApiException,该异常可以在DisposableObserver类的onError方法接收到;如果服务端返回的是成功的状态码,则对数据进行解析,最终返回data。
另外,ParameterizedTypeImpl是一个静态内部类(后面源码会放出),将基本泛型类型指定为BaseResponse,也就是说,原本

Observable<BaseResponse<ArticleBean>> getArticleList(@Path("pageIndex") int pageIndex);

现在可以写成

Observable<ArticleBean> getArticleList(@Path("pageIndex") int pageIndex);

去掉BaseResponse这一层。然后我们现在可以将rxjava的请求订阅也统一封装一下,请求错误的处理。

自定义DisposableObserver

public abstract class BaseResourceObserver<T> extends DisposableObserver<T> {

    /*========================= HttpException 异常 code ==========================*/

    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;

    @Override
    protected void onStart() {
        super.onStart();
    }

    @Override
    public void onError(Throwable throwable) {
        //打印日志到控制台
        throwable.printStackTrace();
        //如果你某个地方不想使用全局错误处理,
        //则重写 onError(Throwable) 并将 super.onError(e); 删掉
        //如果你不仅想使用全局错误处理,还想加入自己的逻辑,
        //则重写 onError(Throwable) 并在 super.onError(e); 后面加入自己的逻辑
        String msg = requestHandle(throwable);
        Log.i("tag",msg);
    }

    @Override
    public void onComplete() {
    }

	/**
     * 统一处理Throwable
     * @param e e
     * @return msg
     */
    private String requestHandle(Throwable e) {
        String msg;
        if (e instanceof HttpException) {
            HttpException httpException = (HttpException) e;
            switch (httpException.code()) {
                case UNAUTHORIZED:
                case FORBIDDEN:
                case NOT_FOUND:
                case REQUEST_TIMEOUT:
                case GATEWAY_TIMEOUT:
                case INTERNAL_SERVER_ERROR:
                case BAD_GATEWAY:
                case SERVICE_UNAVAILABLE:
                default:
                    msg = "服务器错误";
                    break;
            }
        } else if (e instanceof ApiException) {
            //后台异常,在这里你可以toast弹窗或者进行其他处理,具体需根据业务结合
            ApiException apiException = (ApiException) e;
            msg = apiException.getMessage();
        } else if (e instanceof JsonParseException || e instanceof JSONException 
        || e instanceof ParseException) {
            msg = "解析错误";
        } else if (e instanceof ConnectException || e instanceof SocketTimeoutException 
        || e instanceof UnknownHostException) {
            msg = "连接失败,请检查网络";
        }  else if (e instanceof NumberFormatException){
            msg = "数字格式化异常";
        } else {
            msg = "请求失败";
        }
        return msg;
    }
}

替换我们自定义的Converter

Retrofit retrofit = new Retrofit.Builder()
                .client(builder.build())
                .baseUrl(ApiService.BASE_URL)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                //.addConverterFactory(GsonConverterFactory.create())
                //这里是自定义的GsonConverterFactory
                .addConverterFactory(MyConverterFactory.create())
                .build();

在activity请求,

Disposable disposable = ApiServiceImpl.getInstance()
                .getArticleList(pageIndex)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                // 这里使用的是subscribeWith操作符
                .subscribeWith(new BaseResourceObserver<ArticleBean>() {
                    @Override
                    public void onNext(ArticleBean articleBean) {
                       // do something
                    }
                });

在subscribeWith里面初始化我们的BaseResourceObserver,只需重写onNext方法即可,简单便捷。
OK,至此处理json解析异常算是完成了。

总结

1、请求成功基本数据类型一般是不变的;
2、请求失败,只解析code错误码,不解析data,这样就不会出现解析异常了。
最后附上simple源码 RetrofitConverterSimple

参考
Rxjava、Retrofit返回json数据解析异常处理

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值