工作填坑 - Retrofit 同时支持对 Json 格式和原始数据
最近在封装 HTTP 请求的时候,发现了一个挺悲催的事情,服务端返回的数据,有时候是 json 格式,有时候又不是 json 格式,身为乙方的我,又没办法要求甲方去修改成统一的 json 格式,可是如果统一返回原始数据,又感觉要做太多的无用代码,毕竟使用的是 retrofit + rxjava+ OKhttp ,得充分发挥他们三个的作用才行。
然后就想着自己封装的框架能不能同时兼容 Json 格式和非 Json 格式呢?
感觉应该是可以的,因为我们通常使用 Retrofit 解析 json 格式是按照下面的方式,
retrofit = new Retrofit.Builder()
.baseUrl(sBaseUrl)
.addConverterFactory(GsonConverterFactory.create(buildGson()))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(httpClientBuilder.build())
.build();
里面都是 addConverterFactory() 和 addCallAdapterFactory() ,是 add 而不是 set ,并且通过看源码也能看出来,是把 Factory 添加到一个集合里面,所以,应该是可以支持多次 add 的,
public Builder addConverterFactory(Converter.Factory factory) {
converterFactories.add(checkNotNull(factory, "factory == null"));
return this;
}
那么就需要研究一下,在什么情况下,使用 GsonConverterFactory 去解析,什么时候,使用另外一种方式解析数据。
在解决这个问题之前,我们就需要了解 addConverterFactory() 到底是干嘛的 它主要是对数据转化用的,请求网络的数据,在这里转换成我们需要的数据类型,
addConverterFactory()
源码解析
闲话少说,看代码咯。
看过 Retofit 代码的人,应该也都知道,执行某个接口的时候,最终会通过动态代理,执行到
ServiceMethod<Object, Object> serviceMethod =(ServiceMethod<Object, Object>) loadServiceMethod(method);
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.adapt(okHttpCall);
如果没看过,可以参考 源码分析 - Retrofit
这三行代码,我们先看 loadServiceMethod()
ServiceMethod<?, ?> loadServiceMethod(Method method) {
ServiceMethod<?, ?> result = serviceMethodCache.get(method);
if (result != null) return result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = new ServiceMethod.Builder<>(this, method).build();
serviceMethodCache.put(method, result);
}
}
return result;
}
因为首次调用该接口的时候, serviceMethodCache 没有该方法,所以会执行到 build() 方法,创建一个,然后添加到 serviceMethodCache 里面。 Retrofit 中对 addCallAdapterFactory() 的理解 我们继续查看 build() 方法
public ServiceMethod build() {
callAdapter = createCallAdapter();
responseType = callAdapter.responseType();
...
responseConverter = createResponseConverter();
...
return new ServiceMethod<>(this);
}
其他的我们不比关心,只看着几行代码。 callAdapter = createCallAdapter() 涉及到 addCallAdapterFactory() ,会在另外一篇文章中分析
private Converter<ResponseBody, T> createResponseConverter() {
...
return retrofit.responseBodyConverter(responseType, annotations);
...
}
//Retrofit.java
public <T> Converter<ResponseBody, T> responseBodyConverter(Type type, Annotation[]annotations) {
return nextResponseBodyConverter(null, type , annotations);
}
public <T> Converter<ResponseBody, T> nextResponseBodyConverter(
@Nullable Converter.Factory skipPast, Type type , Annotation[] annotations) {
...
int start = converterFactories.indexOf(skipPast) + 1;
for (int i = start, count = converterFactories.size(); i < count; i++) {
//这行代码是关键
Converter<ResponseBody, ?> converter =
converterFactories.get(i).responseBodyConverter(type, annotations , this);
if (converter != null) {
return (Converter<ResponseBody, T>) converter;
}
}
...
throw new IllegalArgumentException(builder.toString());
}
流程就是
- 先通过 addConverterFactory() 往 converterFactories 中添加Factory
- 在调用该接口的时候,第一次缓存中没有,通过 ServiceMethod build() 方法创建一个
- 在创建的过程中,通过 createResponseConverter() 创建一个 Converter
- createResponseConverter() 其实就是遍历 converterFactories 这个集合,找到第一个匹配 Converter 直接返回,不再遍历。匹配的依据就是根据 Converter.Factory中的 responseBodyConverter() 这个方法
- 遍历结束没找到合适的,就直接抛出异常。
所以,如果我们想要定义一种新的数据转换的类,分两步。
- 继承 Converter.Factory 这个抽象类,然后实现 responseBodyConverter() , 返回一个 Converter
- 把这个类添加到 converterFactories 这个集合的首位。
自定义数据转换类
主要包括三个, RawConverterFactory 和 RawResponseBodyConverter 以及 RawRequestBodyConverter
RawConverterFactory
public final class RawConverterFactory extends Converter.Factory {
public static RawConverterFactory create() {
return create(new Gson());
}
@SuppressWarnings("ConstantConditions") // Guarding public API nullability.
public static RawConverterFactory create(Gson gson) {
if (gson == null) {
throw new NullPointerException("gson == null");
}
return new RawConverterFactory(gson);
}
private final Gson gson;
private RawConverterFactory(Gson gson) {
this.gson = gson;
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type responseType, Annotation[] annotations,
Retrofit retrofit) {
if (responseType == String.class) {//返回类型是字符串,
return new RawResponseBodyConverter<>();
} else {
return null;
}
}
@Override
public Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new RawRequestBodyConverter<>(gson, adapter);
}
}
- responseBodyConverter() 中进行判断,如果 responseTyp 的类型是 String ,则认为是需要原始数据,则直接返回一个 RawResponseBodyConverter ,
- 因为请求数据格式是 json 样式,所以 requestBodyConverter() 实现的和 GsonRequestBodyConverter 类似,如果请求格式改了,例如 xml 的话,可以在这里进行处理
RawResponseBodyConverter
这个 RawResponseBodyConverter 是 Converter 的实现类,在 convert() 中直接返回原始数据。
final class RawResponseBodyConverter<T> implements Converter<ResponseBody, T> {
RawResponseBodyConverter() {
}
@Override
public T convert(ResponseBody value) throws IOException {
String response = value.string();
return (T) response;
}
}
因为 RawConverterFactory 是首先 add 的,并且返回的参数是 String 类型,所以就使用 RawConverterFactory 来进行数据的处理
RawRequestBodyConverter
这里没有直接使用 GsonRequestBodyConverter 而是重新定义了一个 RawRequestBodyConverter ,是因为 GsonRequestBodyConverter 不是 public 。
final class GsonRequestBodyConverter<T> implements Converter<T, RequestBody> {}
不能直接使用。只好重新 copy 一份。
final class RawRequestBodyConverter<T> implements Converter<T, RequestBody> {
private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
private static final Charset UTF_8 = Charset.forName("UTF-8");
private final Gson gson;
private final TypeAdapter<T> adapter;
RawRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}
@Override
public RequestBody convert(T value) throws IOException {
Buffer buffer = new Buffer();
Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
JsonWriter jsonWriter = gson.newJsonWriter(writer);
adapter.write(jsonWriter, value);
jsonWriter.close();
return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
}
}
使用
当我们定义返回类型是 String 的时候,就返回的是原始数据,如果是一个其他实体类的话,返回的就是该对象。
// 得到 Json 格式数据,最终通过 GsonConverterFactory 来处理
@Headers("Content-type:application/json")
@POST("/sinoapi/updatepwd")
Observable<ModifyPwdResult> updatepwd(@Body ReqModifyPwd ben);
//得到原始数据,最终通过 RawConverterFactory 来处理
@Multipart
@POST("/sinoapi/uploadimg")
Observable<String> updatePic(@PartMap Map<String, RequestBody> requestBodyMap, @Part MultipartBody.Part imgs);
添加 RawConverterFactory 这个 Factory ,再添加 。
retrofit = new Retrofit.Builder()
.baseUrl(sBaseUrl)
.addConverterFactory(RawConverterFactory.create(buildGson()))
.addConverterFactory(GsonConverterFactory.create(buildGson()))
//支持RxJava2
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(httpClientBuilder.build())
.build();
齐活。
搬运地址:
既已览卷至此,何不品评一二: