[置顶] Retrofit 源码分析流程
2017-07-30 18:03
405 查看
前言
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");
当你调用接口文件中的方法返回一个Call对象或者其他对象(Call对象被转化过去的)的时候执行的流程
Created with Raphaël 2.1.0Api接口文件通过动态代理ApiProxy对象InvocationHandler实现类拦截到方法method1,method2,method3通过Method对象生成ServiceMethod对象通过ServiceMethod和方法参数args对象生成OkHttpCall对象(6)获取到方法返回值(可能是OkHttpCall对象也可能是转化之后的其他对象比如Observable)结束
上面流程图的第(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() 方法,下面我画一下流程图
Created with Raphaël 2.1.0调用call.execute()或者enqueue(Callback<T> callback)方法实际上调用OkHttp中的Call对象的execute() 或者 enqueue(Callback<T> callback)方法请求服务器返回数据,对数据进行包装成一个ResponseBody 对象(4)利用Retrofit中的Converter转化器将ResponseBody对象转化为其他对象结束
上面的(4)至关重要,这里是对数据就行转换的关键.我们使用过Retrofit的同学都知道,我们的Retrofit可以通过添加转化器对返回的json、xml等数据都可以转化成想要的数据.比如常见的json转化成为实体对象供我们使用
流程总结
1.对于Retrofit 有两个流程,就是上述的两个.第一个流程是获取一个可请求的对象.这时候请求并没有发送,你获取到的只是一个OkHttp中的Call对象的一个代理对象.这个对象很可能是一个多次代理的对象,其中内部的OkHttpCall就是对OkHttp中的Call对象的第一层代理.如果你使用了RxJava,你返回的ObServable 对象就是对OkHttpCall的二次代理.而 OkHttp中的Call 是一个可请求的对象,内部封装并且持有了一个Request
2.第二个流程就是你拿到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 的拦截器,让所有的方法都走另一个网络框架的请求,也是可以的。
相关文章推荐
- [置顶] Activity启动流程源码分析之入门(一)
- [置顶] 【Android okhttp源码解析 二】同步请求流程和源码分析
- [置顶] HBase源码分析之HRegion上MemStore的flsuh流程(二)
- [置顶] 【Android okhttp源码解析 三】异步请求流程和源码分析
- 源码分析Retrofit请求流程
- [置顶] DecorView绘制流程源码分析
- [置顶] View绘制三大流程源码分析
- [置顶] Activity布局加载流程源码分析(II)
- [置顶] Activity布局加载流程源码分析(I)
- [置顶] 带你从源码角度分析ViewGroup中事件分发流程
- [置顶] Activity启动流程源码分析(应用中)
- [置顶] Activity启动流程源码分析(Launcher中)
- Ceph学习——Librbd块存储库与RBD读写流程源码分析
- Android:这是一份全面 &amp; 详细的Retrofit 2.0 源码分析指南
- Fragment之添加显示流程源码分析
- Android笔记--View绘制流程源码分析(二)
- SpringMVC源码分析(3)DispatcherServlet的请求处理流程 推荐
- Tor源码分析九 -- 客户端执行流程(网络信息的下载)
- HBase源码分析之regionserver读取流程分析
- android6.0源码分析之Camera API2.0下的Capture流程分析