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 优雅地处理后台返回的骚数据