问题:生产环境上 后端返回数据接口 异常
工作中经常会发生 线上有些页面 突然就不能正常访问了,然后一番检查后发现是因为后端返回的数据接口发生了改变,导致了 数据解析失败进而 页面不能正常访问。(注:这种情况 在后端语言是 PHP的会后 会出现的更加频繁)。
解决方式
其实这个问题的解决 或者说 预防这种问题的发生方式有很多,比如
- 后端开发人员 在 提交代码时 有专门的的工具能检测出这种异常。这是最优的
- 测试部门 或者 后端 开发 接口监控脚本,然后定时跑 ,这样能再第一时间发现问题
- 移动端 在 访问接口发现问题后 主动报警,然后反馈问题。
今天我们就来说说,在移动端 监控数据解析,异常是主动上报的方案。
思路
对数据进行预解析,如果有问题的及时上报,具体到 Retrofit
框架上的话,就是添加一个 ConverterFactory
来专门做这个事情。
难点
1. Retrofit
中虽然 ConverterFactory
可以添加多个,但是最终真正 解析数据并返回最终结果的 只有一个,那么 怎么让 多个 Converter
被调用而 起作用的只有一个呢?
阅读源码发现Retrofit
中提供了 nextResponseBodyConverter
这个Api 来让开发者或者 下一个 Converter
所以我们就可以 在当前 Converter
中获取 下一个 Converter
来进行真正的 解析工作。这也是我们要注意的一个地方,数据解析的这个 ConverterFactory
必须 在真正 解析数据的 ConverterFactory
之前被添加到 Retrofit
核心代码如下
SerializationCheckerConverterFactory.class
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
//获取下一个 ResponseBodyConverter 因为 BeanCheckerResponseBodyConverter 是不做 真正结果的返回的
Converter<ResponseBody, Object> delegate = retrofit.nextResponseBodyConverter(this, type, annotations);
return new SerializationCheckerResponseBodyConverter<>(delegate, gson, type,annotations,retrofit,listener);
}
-----------
SerializationCheckerResponseBodyConverter.class
@Override
public T convert(ResponseBody value) throws IOException {
//创建一个新的 ResponseBody 给后面的 Converter 使用
MediaType contentType = value.contentType();
String bodyString = value.string();
ResponseBody newResponseBody = ResponseBody.create(contentType, bodyString);
StringReader reader = new StringReader(bodyString);
JsonReader jsonReader = gson.newJsonReader(reader);
try {
adapter.read(jsonReader);
if (jsonReader.peek() != JsonToken.END_DOCUMENT) {
throw new JsonIOException("JSON document was not fully consumed.");
}
return delegate.convert(newResponseBody);
} catch (Exception e) {
String errorMessage = StringUtils.isNotEmpty(e.getMessage()) ? e.getMessage() : e.getLocalizedMessage();
if (listener != null) {
listener.onSerializationFailed(requestUrl, errorMessage, annotations, retrofit);
}
return delegate.convert(newResponseBody);
} finally {
value.close();
}
}
源码地址
源码存放在GitHub上,有需要自取