前言
Retrofit 框架使用了有一年多了,但是说来惭愧,一直都是处于使用的状态,不会用的地方利用百度或者谷歌搜索一下.一直没有去摸索里面的源代码.这几天我对源代码进行阅读之后,不仅理顺了Retrofit 框架的实现,而且对泛型的认识提升到了一个新的高度.因为里面很多代码是对泛型进行处理的.下面就容许我给大家梳理一下Retrofit 的工作原理
源码分析
首先我定义一个接口文件
public interface Api {
@GET()
Call<ResponseBody> get(@Url String uri);
}
我为了演示方便我是在Java环境下运行Retrofit的,并不是Android环境哦
public class Main {
public static void main(String[] args)
throws IOException, NoSuchMethodException {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://publicobject.com")
.build();
Api api = retrofit.create(Api.class);
retrofit2.Call<ResponseBody> call = api.get("helloworld.txt");
Response<ResponseBody> response = call.execute();
System.out.println("result = " + response.body().string());
}
}
首先是当你调用Api接口中的方法拿到Call对象的时候(之后可能是其他对象)
// 这是默认的没有加上任何CallFactory转化器和ConverterFactory和转化器的,ResponseBody对象是OkHttp提供的
retrofit2.Call<ResponseBody> call = api.get("helloworld.txt");
上面流程图的第(6)步就是Retrofit 内置的CallAdapter 转化器的作用啦.
原本默认返回的是一个Call对象,而我们可以定义一个CallAdapter转换器来将Call对象转化为其他对象,比如我们的RxJava中的ObServable
上述就是当你调用上面那句话的时候,会执行的流程.具体源码后面分析
执行请求
Response<ResponseBody> response = call.execute();
System.out.println("result = " + response.body().string());
result =
\\ //
\\ .ooo. //
.@@@@@@@@@.
:@@@@@@@@@@@@@:
:@@. '@@@@@' .@@:
@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@
:@@ :@@@@@@@@@@@@@@@@@. @@:
@@@ '@@@@@@@@@@@@@@@@@, @@@
@@@ '@@@@@@@@@@@@@@@@@, @@@
@@@ '@@@@@@@@@@@@@@@@@, @@@
@@@ '@@@@@@@@@@@@@@@@@, @@@
@@@ '@@@@@@@@@@@@@@@@@, @@@
@@@ '@@@@@@@@@@@@@@@@@, @@@
@@@@@@@@@@@@@@@@@
'@@@@@@@@@@@@@@@'
@@@@ @@@@
@@@@ @@@@
@@@@ @@@@
'@@' '@@'
:@@@.
.@@@@@@@: +@@ `@@ @@` @@ @@
.@@@@'@@@@: +@@ `@@ @@` @@ @@
@@@ @@@ +@@ `@@ @@` @@ @@
.@@ @@: +@@ @@@ `@@ @@` @@@@@@ @@@@@@ @@;@@@@@
@@@ @@@ +@@ @@@ `@@ @@` @@@@@@ @@@@@@ @@@@@@@@@
@@@ @@@ +@@ @@@ `@@@@@@@@@@` @@ @@ @@@ :@@
@@@ @@@ +@@@@@ `@@@@@@@@@@` @@ @@ @@# @@+
@@@ @@@ +@@@@@+ `@@ @@` @@ @@ @@: @@#
@@: .@@` +@@@+@@ `@@ @@` @@ @@ @@# @@+
@@@. .@@@ +@@ @@@ `@@ @@` @@ @@ @@@ ,@@
@@@@@@@@@ +@@ @@@ `@@ @@` @@@@ @@@@ @@@@#@@@@
@@@@@@@ +@@ #@@ `@@ @@` @@@@: @@@@: @@'@@@@@
@@:
@@:
@@:
那么这时候我们我们调用的代码执行的流程是怎么样的呢?
我们上面说过调用接口的方法返回的是一个Call对象或者是其他一个转化过后的对象,我们由于还没有添加转化器,所以默认就只能返回Call对象。
同时我们上面的流程图也介绍了默认返回的Call(其实就是OkHttpCall,实现了Retrofit中的Call接口)其实就是对OkHttp中的Call对象中的一个包裹(也可以说是一个代理)
所以当你执行下面的代码
Response<ResponseBody> response = call.execute();
其实就会调用到OkHttp中的Call对象的 execute() 方法,下面我画一下流程图
上面的(4)至关重要,这里是对数据就行转换的关键.我们使用过Retrofit的同学都知道,我们的Retrofit可以通过添加转化器对返回的json、xml等数据都可以转化成想要的数据.比如常见的json转化成为实体对象供我们使用
流程总结
1.对于Retrofit 有两个流程,就是上述的两个.第一个流程是获取一个可请求的对象.这时候请求并没有发送,你获取到的只是一个OkHttp中的Call对象的一个代理对象.这个对象很可能是一个多次代理的对象,其中内部的OkHttpCall就是对OkHttp中的Call对象的第一层代理.如果你使用了RxJava,你返回的ObServable 对象就是对OkHttpCall的二次代理.
而 OkHttp中的Call 是一个可请求的对象,内部封装并且持有了一个Request2.第二个流程就是你拿到Retrofit的默认对OkHttpCall代理的Call对象(和OkHttp的Call是两个类,不要混淆) 或者经过转化的其他对象,比如ObServable
如果是默认的Call的话,调用enqueue方法或者execute方法即可请求网络拿到数据.
如果是ObServable 只需要订阅即可,就可以拿到数据
阅读源码证明上述流程
public static void main(String[] args) throws IOException, NoSuchMethodException {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://publicobject.com")
.build();
Api api = retrofit.create(Api.class);
Call<ResponseBody> call = api.getCall("helloworld.txt");
Response<ResponseBody> response = call.execute();
System.out.println("result = " + response.body().string());
}
首先上面的代码是成功请求的代码
流程1
我们可以看到首先构建了一个Retrofit,然后
Api api = retrofit.create(Api.class);
这句代码发生了什么呢?
代理接口
有Java动态代理知识的同学可以很明显发现这里使用了动态代理.对接口Api进行了一个代理.
而匿名内部类实现了InvocationHandler 接口就是拦截到所有的方法
ServiceMethod对象的创建
然后我们往下看,我们发现通过方法loadServiceMethod(method)创建了ServiceMethod对象,ServiceMethod对象内部根据method中解析了注解信息和返回值类型,并且根据参数中的注解为每一个参数生成一个处理对象,构建ServiceMethod是采用了建造者模式
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;
}
public ServiceMethod build() {
callAdapter = createCallAdapter();
responseType = callAdapter.responseType();
if (responseType == Response.class || responseType == okhttp3.Response.class) {
throw methodError("'"
+ Utils.getRawType(responseType).getName()
+ "' is not a valid response body type. Did you mean ResponseBody?");
}
responseConverter = createResponseConverter();
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}
if (httpMethod == null) {
throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");
}
if (!hasBody) {
if (isMultipart) {
throw methodError(
"Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
}
if (isFormEncoded) {
throw methodError("FormUrlEncoded can only be specified on HTTP methods with "
+ "request body (e.g., @POST).");
}
}
int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];
for (int p = 0; p < parameterCount; p++) {
Type parameterType = parameterTypes[p];
if (Utils.hasUnresolvableType(parameterType)) {
throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
parameterType);
}
Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
if (parameterAnnotations == null) {
throw parameterError(p, "No Retrofit annotation found.");
}
parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
}
if (relativeUrl == null && !gotUrl) {
throw methodError("Missing either @%s URL or @Url parameter.", httpMethod);
}
if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
throw methodError("Non-body HTTP method cannot contain @Body.");
}
if (isFormEncoded && !gotField) {
throw methodError("Form-encoded method must contain at least one @Field.");
}
if (isMultipart && !gotPart) {
throw methodError("Multipart method must contain at least one @Part.");
}
return new ServiceMethod<>(this);
}
loadServiceMethod(method) 这个方法内部对获取ServiceMethod进行了缓存,这样子我们调用同一个方法的时候就不会多次解析method中的注解信息,浪费时间
后面的build方法是构建出ServiceMethod对象的Builder对象的build方法。可以看到里面对方法上的注解信息的解析和各种健壮性的判断
OkHttpCall(其实就是Retrofit中的Call接口的实现类) 对象的创建
创建OkHttpCall对象我们传入了ServiceMethod对象和实际调用方法的时候的参数数组.
上面说过ServiceMethod解析了方法上的注解信息和生成了各个参数的处理类
(每一个处理类还会遍历所有的转化器,找到第一个支持解析接口中方法参数类型的Converter)
还包括找出了可以转化Api接口中写的泛型中的对象的转化器Converter
现在有ServiceMethod 和 调用方法的参数args 那么就可以生成一个真正的请求对象了.
ServiceMethod 中的信息 + ServiceMethod中的参数处理类处理args得到的信息 = Request对象
OkHttpCall 中有一段代码
private okhttp3.Call createRawCall() throws IOException {
Request request = serviceMethod.toRequest(args);
okhttp3.Call call = serviceMethod.callFactory.newCall(request);
if (call == null) {
throw new NullPointerException("Call.Factory returned null.");
}
return call;
}
从这里可以看到调用了ServiceMethod 对象的toRequest方法生成了一个Request对象,用过OkHttp的同学就很清楚了,这里的Request对象就是OkHttp的,然后接下来对这个Request包裹了一下,变成了 okhttp中的Call对象.而 这个call是OkHttpCall对象持有的,所以之前说OkHttpCall是OkHttp中的Call对象的一个代理类,这里得到了验证.
然后下面是转化Call的代码
这里的代码看方法名字其实很清楚,就是对OkHttp做转换。里面会遍历所有的CallAdapter,如果某一个CallAdapter 可以支持生成你在接口文件中写的类型,那么就获取到这个CallAdapter 让他对OkHttpCall 对象做转化,这也就是下面代码能返回ObServable 对象的原因
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://publicobject.com")
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
Api api = retrofit.create(Api.class);
Observable<ResponseBody> observable = api.get("helloworld.txt");
public interface Api {
@GET()
Observable<ResponseBody> get(@Url String uri);
}
流程2
这时候参数也解析了.请求也有了,就差流程2调用啦,流程2的代码就不一一展示了,但是展示一个最关键的返回的数据类型的转化的部分
上面陈述了OkHttpCall 是对OkHttp中的Call对象的一个代理,所以OkHttpCall内部执行请求之后就是拿到返回值最近的地方,让我们看看,我们以同步的方法为例子:
public Response<T> execute() throws IOException {
okhttp3.Call call;
synchronized (this) {
if (executed) throw new IllegalStateException("Already executed.");
executed = true;
if (creationFailure != null) {
if (creationFailure instanceof IOException) {
throw (IOException) creationFailure;
} else {
throw (RuntimeException) creationFailure;
}
}
call = rawCall;
if (call == null) {
try {
call = rawCall = createRawCall();
} catch (IOException | RuntimeException e) {
creationFailure = e;
throw e;
}
}
}
if (canceled) {
call.cancel();
}
return parseResponse(call.execute());
}
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
ResponseBody rawBody = rawResponse.body();
// Remove the body's source (the only stateful object) so we can pass the response along.
rawResponse = rawResponse.newBuilder()
.body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
.build();
int code = rawResponse.code();
if (code < 200 || code >= 300) {
try {
// Buffer the entire body to avoid future I/O.
ResponseBody bufferedBody = Utils.buffer(rawBody);
return Response.error(bufferedBody, rawResponse);
} finally {
rawBody.close();
}
}
if (code == 204 || code == 205) {
rawBody.close();
return Response.success(null, rawResponse);
}
ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
try {
T body = serviceMethod.toResponse(catchingBody);
return Response.success(body, rawResponse);
} catch (RuntimeException e) {
// If the underlying source threw an exception, propagate that rather than indicating it was
// a runtime exception.
catchingBody.throwIfCaught();
throw e;
}
}
ServiceMethod 对象中的方法
/** Builds a method return value from an HTTP response body. */
R toResponse(ResponseBody body) throws IOException {
return responseConverter.convert(body);
}
可以看到最后的代码中,又通过 ServiceMethod 对象的toResponse 方法生成一个Response对象,内部利用 ServiceMethod 持有的类型转化器 Converter 对ResponseBody 进行了转化,比如你添加了Gson的转化器,就会帮你转化成对象.不添加任何一个转化器的时候,默认返回的是一个ResponseBody 对象。这在最开始就展示了.
总结
好处:
Retrofit 提供了很高的扩展性,让我们可以定制返回的对象和返回的结果.这可以和轻易的和其他框架搭配使用,影响力比较大的就是和RxJava搭配使用.
同时你还可以自定义转化器 Converter,让你的接口方法中的参数支持更多的参数类型,比如File,Map,List等等,虽然一些集合内部已经支持了.但我就是举个例子.嘿嘿嘿
不足之处:
内部就是基于OkHttp 实现的,并不能让Retrofit 支持其他的网络框架,比如 HttpUrlConnection
如果非要使用Retrofit但是内部使用的是其他的网络框架的话,只能使用 内部的 OkHttp 的拦截器,让所有的方法都走另一个网络框架的请求,也是可以的。