转自:
Retrofit 2.0 自定义Converter
本文原创作者:一叶飘舟 作者博客地址:http://blog.csdn.net/jdsjlzx
在Retrofit中,无论是发送数据还是接收数据,最后,都是通过OkHttp的RequestBody或者ResponseBody来实现的,在实际项目中,有时候,原始的ReqeustBody或者ResponseBody无法满足我们的需求(比如: 接口需要加解密的时候),这时,就需要自定义ConverterFactory.
- requestBodyConverter 不执行的解决办法:
参数要使用@Body这种形式,否则 request 方法会不起作用。
P.S. 笔者注: Google的GsonConverterFactory就是很好的自定义ConverterFactory的例子,我们可以阅读其源码,研究如何自定义ConverterFactory
在Retrofit通过build()构造者模式,得到Retroft实例时,可以通过该addConverterFactory添加多个ConverterFactory,但是,这里需要注意的是,添加到顺序是有影响程序运行的,见下面代码
.addConverterFactory(GsonConverterFactory.create())
源码:
private List converterFactories = new ArrayList<>();
...
public Builder addConverterFactory(Converter.Factory factory) {
converterFactories.add(checkNotNull(factory, "factory == null"));
return this;
}
在nextRequestBodyConverter(...) 方法 和 nextResponseBodyConver(...)方法在,可以看到:
按照retrofit的逻辑,是从前往后进行匹配,如果匹配上,就忽略后面的,直接使用。
int start = converterFactories.indexOf(skipPast) + 1;
for (int i = start, count = converterFactories.size(); i < count; i++) {
Converter.Factory factory = converterFactories.get(i);
Converter, RequestBody> converter =
factory.requestBodyConverter(type, parameterAnnotations, methodAnnotations, this);
if (converter != null) {
//noinspection unchecked
return (Converter) converter;
}
}
从上面源码中可见,当factofy.requestBodyConvert(...)返回null时,表示没有匹配上,for循环会取出下一个factofy,因此,我们自定义ConverterFactory时候,要进行条件判断,符合我们的一定规则,才能使用,
所以说,如果你有2个ConverterFactory,都可以处理数据,那么,先添加的会生效
P.S. 笔者注:
这里说明下,为什么: 返回null,表示没匹配上,因为在抽象类Convert.Factory中(是所有自定义ConverFactory的基类),responseBodyConverter(...)/requestBodyConvertFactory(...) 默认是返回null的
public interface Converter {
T convert(F value) throws IOException;
abstract class Factory {
public Converter responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
return null;
}
public Converter, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
return null;
}
public Converter, String> stringConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
return null;
}
}
}
Retrofit 官方给了几个常用的转换库:
Gson: com.squareup.retrofit2:converter-gson
Jackson: com.squareup.retrofit2:converter-jackson
Moshi: com.squareup.retrofit2:converter-moshi
Protobuf: com.squareup.retrofit2:converter-protobuf
Wire: com.squareup.retrofit2:converter-wire
Simple XML: com.squareup.retrofit2:converter-simplexml
Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars
自定义ConverterFactory
我们以创建适配ProtoBuff协议的ConvertFactory为例
P.S. 科普一下Proto Buffs
这是google开源的一个项目
Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 三种语言的 API。
protobuf协议是以一个 .proto 后缀的文件为基础,这个文件描述了存在哪些数据,数据类型是怎么样的。
比json格式的数据,xml格式的数据 性能更好,效率更高
一条消息数据,用protobuf序列化后的大小是json的10分之一,xml格式的20分之一,是二进制序列化的10分之一(极端情况下,会大于等于直接序列化),总体看来ProtoBuf的优势还是很明显的
Retrofit已经为我们提供了自定义ConverterFactory的接口,我们只需要实现它给的接口即可。
public final class ProtoConverterFactory extends Converter.Factory {
public static ProtoConverterFactory create() {
return new ProtoConverterFactory();
}
@Override
public Converter responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
//进行条件判断,如果传进来的Type不是class,则匹配失败
if (!(type instanceof Class>)) {
return null;
}
//进行条件判断,如果传进来的Type不是MessageLite的实现类,则也匹配失败
Class> c = (Class>) type;
if (!MessageLite.class.isAssignableFrom(c)) {
return null;
}
Parser parser;
try {
Field field = c.getDeclaredField("PARSER");
//noinspection unchecked
parser = (Parser) field.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new IllegalArgumentException(
"Found a protobuf message but " + c.getName() + " had no PARSER field.");
}
//返回一个实现了Converter的类,
return new ProtoResponseBodyConverter<>(parser);
}
@Override
public Converter, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
//进行条件判断,如果传进来的Type不是class,则匹配失败
if (!(type instanceof Class>)) {
return null;
}
//进行条件判断,如果传进来的Type不是MessageLite的实现类,则也匹配失败
if (!MessageLite.class.isAssignableFrom((Class>) type)) {
return null;
}
return new ProtoRequestBodyConverter<>();
}
}
注意在Convert 接口的两个泛型中,前一个类型是传进来的对象类型,后一个类型是转换后的对象类型。
也就是说Convert 方法,要实现的内容,就是吧 前一个类型T -> F 的操作
final class ProtoRequestBodyConverter implements Converter {
private static final MediaType MEDIA_TYPE = MediaType.parse("application/x-protobuf");
@Override public RequestBody convert(T value) throws IOException {
byte[] bytes = value.toByteArray();
return RequestBody.create(MEDIA_TYPE, bytes);
}
}
final class ProtoResponseBodyConverter
implements Converter {
private final Parser parser;
ProtoResponseBodyConverter(Parser parser) {
this.parser = parser;
}
@Override public T convert(ResponseBody value) throws IOException {
try {
return parser.parseFrom(value.byteStream());
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException(e); // Despite extending IOException, this is data mismatch.
} finally {
value.close();
}
}
}
小结:
自定义ConverterFactory的几个关键如下:
- 实现ConverterFactory 里的 两个方法
- 方法内,根据方法参数,判断是否要处理,不处理的话,要返回null,以便retrofit能从ConverterFactofy列表里取下一个Factory实例
- Convert接口的两个泛型,要区分好,哪个是in 哪个是out
P.S. 作者的文章很长,我拆分成几个部分,免得看的时候失去耐心.