Android使用Retrofit框架时JSON解析出错的解决方案

前言

Retrofit 是目前主流的网络请求框架,相信用过的小伙伴都可能会遇到这样的问题,绝大部分接口测试都正常,就个别接口尤其是返回失败信息时报了个奇怪的错误信息,而看了自己的代码逻辑也没什么问题。那是什么原因呢?是后台返回的数据有误吗?还是自己在处理这些失败数据的时候没考虑仔细呢?

问题原因

后台返回的失败数据不是自己期望的数据格式,比如说,代码中实体bean长这样:

public class BaseResponse<T> {
    private boolean success;
    private String code;
    private String message;
    private T data;
}

正常返回正确的情况下,服务器返回的data数据是JSON字符串,T 就很顺利的转换成我们对应的回调实体bean;但当正常返回失败的情况下,服务器的回调可能就长这样:

{
  "success": false,
  "code":1001,
  "message": "登录失败",
  "data": []
}

怎么办,这时候我们的程序就会报一个 Net_OnError:java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 44 path $.data 这样的JSON解析错误的bug了,皆因服务器返回的 data 是一个空数组,不再是JSON字符串的格式,那么如何解决呢?

解决方案

1.最直接的办法:找后台开发的同事交流沟通一下,把 [] (空数组)或者其他数据类型改成 {} (空JSON字符串)类型,再返回给我们,这时候就可以解决这个JSON解析错误的问题了。
2.自行解决方案:加请求拦截器,在JSON数据解析之前把格式给改了,也可以解决这个问题。

自行解决具体方案

以下是参考 Android 优雅地处理后台返回的骚数据 这篇文章的,实测有效。
(1)写一个抽象请求回调体的拦截器:

public abstract class ResponseBodyInterceptor implements Interceptor {

    @NotNull
    @Override
    public Response intercept(@NotNull Chain chain) throws IOException {
        Request request = chain.request();
        String url = request.url().toString();
        Response response = chain.proceed(request);
        ResponseBody responseBody = response.body();
        if (responseBody != null) {
            long contentLength = responseBody.contentLength();
            BufferedSource source = responseBody.source();
            source.request(Long.MAX_VALUE);
            Buffer buffer = source.getBuffer();

            if ("gzip".equals(response.headers().get("Content-Encoding"))) {
                GzipSource gzippedResponseBody = new GzipSource(buffer.clone());
                buffer = new Buffer();
                buffer.writeAll(gzippedResponseBody);
            }

            MediaType contentType = responseBody.contentType();
            Charset charset;
            if (contentType == null || contentType.charset(StandardCharsets.UTF_8) == null) {
                charset = StandardCharsets.UTF_8;
            } else {
                charset = contentType.charset(StandardCharsets.UTF_8);
            }

            if (charset != null && contentLength != 0L) {
                return intercept(response, url, buffer.clone().readString(charset));
            }
        }
        return response;
    }

    abstract Response intercept(@NotNull Response response, String url, String body);
}

(2)根据服务器不同的回调实现我们需要的请求回调体拦截器,如我当前的项目:

public class HandleErrorInterceptor extends ResponseBodyInterceptor {

    @Override
    Response intercept(@NotNull Response response, String url, String body) {
        Log.w("hao", "拦截器拦到的返回数据:" + body);
        JSONObject jsonObject = null;
        try {
            jsonObject = new JSONObject(body);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        if (jsonObject != null) {
            if (!jsonObject.optBoolean("success")) {
                Log.w("hao", "返回失败=============");
                if (response.body() != null) {
                    try {
                        String json = response.body().string().replace("\"data\":[]", "\"data\":{}");
                        ResponseBody boy = ResponseBody.create(response.body().contentType(), json);
                        return response.newBuilder().body(boy).build();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return response;
    }
}

其中这里 String json = response.body().string().replace("“data”:[]", ““data”:{}”); 就是根据服务器返回的不同数据类型进行替换,最后是使用这个拦截器。
(3)

	okHttpClient = new OkHttpClient.Builder()
//                .addInterceptor(getInterceptor())
                .addInterceptor(new HandleErrorInterceptor())   //处理错误回调的拦截器
                .readTimeout(30, TimeUnit.SECONDS)
                .writeTimeout(30, TimeUnit.SECONDS)
                .connectTimeout(30, TimeUnit.SECONDS)
                .retryOnConnectionFailure(true)
//                .addNetworkInterceptor(getHeaderInterceptor())
//                .addInterceptor(getInterceptor())       //添加网络打印拦截
                .build();

这样请求就能愉快地进行了。

参考文档

Android 优雅地处理后台返回的骚数据

你可能感兴趣的:(笔记,备忘录)