问题
Retrofit 是目前主流的网络请求框架,使用Retrofit时采用数据转化器直接转化服务器数据时可能会因为服务器接口返回的同一字段数据类型不同而解析错误,这就需要我们开发者处理这种情况,返回数据比如这种:
{
"code": 500,
"msg": "登录失败",
"data": ""
}
{
"code": 500,
"msg": "登录失败",
"data": 0
}
{
"code": 500,
"msg": "登录失败",
"data": []
}
解决方案三种:
解决方案一:
接口人员制定接口规则,同一接口同一字段在不同情况下类型需要保持一致,这样就能很好的解决问题,也有利于后台逻辑清晰条理。
很多事情不同后台配合的人员的个人情况不同,求人不如求己,自己掌控任何时候都能游刃有余的处理好问题,于是就有了方案二和方案三。
解决方案二:
方案二是自定义Gson解析器,涉及三个类:GsonConverterFactory、GsonRequestBodyConverter(处理请求时的参数body)、GsonResponseBodyConverter(处理响应的数据body)
方案二也是很多博客中提到的,具体的做法我会在结尾附上一篇网友的博客,博客中也提到了如果用这种,一旦我们脱离了Retrofit框架,那么可能就无法处理这个数据类型不一致的问题了,那么我们就可以采用方案三的方式,拦截器来提前将数据做一些修改。
解决方案三(以我项目中的使用为例):
import androidx.annotation.NonNull;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSource;
import okio.GzipSource;
public abstract class ResponseBodyInterceptor implements Interceptor {
@NonNull
@Override
public Response intercept(@NonNull 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.buffer();
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(@NonNull Response response,String url, String body);
}
import androidx.annotation.NonNull;
import org.json.JSONException;
import org.json.JSONObject;
import okhttp3.Response;
import okhttp3.ResponseBody;
public class HandleErrorInterceptor extends ResponseBodyInterceptor{
@Override
Response intercept(@NonNull Response response, String url, String body) {
try {
JSONObject jsonObject = new JSONObject(body);
if(!url.startsWith("https://apis.map.qq.com")){
if (!"200".equals(jsonObject.optString("code"))) {
if (response.body() != null) {
JSONObject obj = new JSONObject(body);
if(obj.has("data")){
obj.put("data",null);
}
ResponseBody boy = ResponseBody.create(response.body().contentType(), obj.toString());
return response.newBuilder().body(boy).build();
}
}
}
} catch (JSONException e) {
e.printStackTrace();
}
return response;
}
}
我理解的这种方式关键点在于使用一次流就会被关闭释放,而我们是需要读取返回结果是否符合我们定义的格式或者类型,如果发现不同需要处理成我们解析需要的格式,然后再交给格式转化器去转化,上述代码就是做了这些事情。
写在最后
本文基本引用了Android 优雅地处理后台返回的骚数据这篇文章,这篇文章还大量罗列了遇到的各种服务器返回格式的问题,可以看一看了解一下。