「拆轮子」Retrofit 整体架构、细节和思考
阅读源码基本套路:WHW
What:能做哪些事?提供了什么功能?
How:采用什么方式实现的?由哪些模块组成?
Why:为什么有这样的需求?模块这样封装的意图是什么?还有没有更好的方式?
前言
在业务常用框架中,Retrofit 算是代码量较少,难度较低的开源项目。虽然代码不多,但依然用到了大量的设计模式,具有非常好的扩展性。
解析 Retrofit 源码的优秀文章不少,本文不再赘述,抓住易曲解的概念、整体架构和难理解的细节来做分析。
明确概念
一个简单的网络请求调用流程如下:
//1. 新建一个retrofit对象
Retrofit retrofit=new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build();
...
//2. 用retrofit加工出对应的接口实例对象
ApiService mApiService= retrofit.create(ApiService.class);
//3. 调用接口函数,获得网络工作对象
Call<User> callWorker= mApiService.getUserInfo();
//4. 网络请求入队
callWorker.enqueue(new Callback<User>() {
@Override
public void onResponse(Call<BizEntity> call, Response<User> response) {...}
@Override
public void onFailure(Call<BizEntity> call, Throwable t) {...}
});
我们从上面的应用场景可以看出,Retrofit的作用是按照接口去定制Call网络工作对象,也就是说:Retrofit并不直接做网络请求,只是生成一个能做网络请求的对象。
Retrofit在网络请求中的作用大概可以这样理解:

我们看到,从一开始,Retrofit要提供的就是个Call工作对象。
换句话说,对于给Retrofit提供的那个接口
public interface ApiService {
@POST("url")
Call<User> getUserInfo();
}
这个接口并不是传统意义上的网络请求接口,这个接口不是用来获取数据的接口,而是用来生产对象的接口,这个接口相当于一个工厂,接口中每个函数的返回值不是网络数据,而是一个能进行网络请求的工作对象,我们要先调用函数获得工作对象,再用这个工作对象去请求网络数据。
所以Retrofit的实用价值意义在于,他能根据你的接口定义,灵活地生成对应的网络工作对象,然后你再择机去调用这个对象访问网络。
代码结构
说白了,Retrofit就做了三件事:
- 根据注解解析 ApiService 中每个方法的参数,url,请求类型等信息,组装为 ServiceMethod 类。
- 根据 ServiceMethod 信息 new 请求对象OkHttpCall(内部持有真正的请求对象 okhttp3.Call 的引用,相当于包了一层)。
- 用适合的适配器 callbackExecutor 转换网络请求对象 Call 为我们声明的接口返回类型(如 Call<R> 到 Observable<R>),用适合的转换器 Converter 转换默认的Response为声明的接口返回值(如 Call<ResponseBody> 到 Call<UserInfo>),返回请求对象。
以上加粗的关键类,我们可以详细看下类结构是怎么样的。

可以看到,Retrofit 自身的结构很简单,代码量也不是很大。红色框部分是http包,代表的是所有的Annotation层。 通过这些注解类,把方法里声明的注解一一映射到构造 OkHttp 请求对象所需要的Request参数。
几个主要类的UML简图:
1. Retrofit 和 ServiceMethod

Retrofit 和 ServiceMethod 使用了 Builder模式(省略了 Director 和Abstract Product 的 Builder模式)来构建自己,Retrofit 的作用很简单,传入需要的参数,构建一个 Retrofit 对象,然后通过动态代理的方式,得到我们自定义的方法接口的实例,参数中除了baseUrl 之外,其他都是可选的,如果没设置会使用默认值。
ServiceMethod 作用就是解析Annotation,同时提供方法生成网络请求需要的Request和解析请求结果Response。
2. CallAdapter 和 CallAdapter.Factory

CallAdapt 的作用是把 Call 转变成你想要返回的对象,起作用的是 adapt 方法,CallAdapter.Factory 的作用是获取 CallAdapter 。ExecutorCallAdapterFactory 的 CallAdapter会将回调方法放到主线程中执行,能够接受的返回值类型为Call<T>。很明显,RxJavaCallAdapterFactory的CallAdapter能够接受的返回值是Oservable<T>。如果想让方法直接返回一个对象,可以自定义一个CallAdapter.Factory。
3. Converter和Converter.Factory

Converter 的作用是将网络请求结果 ResponseBody 转换为我们希望的返回值类型。Converter.Factory 的作用是获取 Converter,这里很明显采用了静态工厂模式。
4. OkHttpCall

OkHttpCall 继承自 interface Call,主要的作用是调起执行网络请求以及返回当前请求状态状态,但是真正的网络请求其实在okhttp3.Call接口,接口定义如下:

这个接口的实现类是 okhttp3.RealCall,可以发现,Retrofit 的Call 接口和 okhttp3 的 Call 接口定义几乎是完全一样的,这样做的好处显而易见:利于扩展,解耦。
5. RxJavaCallAdapterFactory

CallAdapterFactory的作用及工作机理前面已经介绍过了,RxJavaCallAdapterFactory的作用也是一样的,只不过RxJavaCallAdapterFactory中内部又定义了三种CallAdapter:ResponseCallAdapter、ResultCallAdapter和SimpleCallAdapter,根据返回值类型决定到底使用哪个,代码如下:

细节点
以上流程中,有很多细节可以详细梳理下。
1. 如何将 ApiService 接口转换为网络请求?
ApiService mApiService= retrofit.create(ApiService.class);
Call<User> callWorker= mApiService.getUserInfo();
看到以上代码,我们不禁提出疑问,这里的mApiService是什么类型?为什么可以直接调用接口方法?create做了什么?生成接口实现类吗?
我们 debug 到 retrofit.create() 中一看究竟:
public <T> T create(final Class<T> service) {
// 检查传入的类是否为接口并且没有继承其他接口
Utils.validateServiceInterface(service);
// 预加载开关,默认关,
if (validateEagerly) {
eagerlyValidateMethods(service);
}
// 重点是这里!
// 首先会返回一个利用代理实现的 ApiService 对象
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
// 我们调用该对象的每一个方法时都会进入到invoke方法里
@Override public Object invoke(Object proxy, Method method, Object... args)
throws Throwable {
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
// 解析当前调用的方法
ServiceMethod serviceMethod = loadServiceMethod(method);
// 将刚刚解析完毕包装后的具体方法封装成 OkHttpCall ,你可以在该实现类找到 okhttp 请求所需要的参数
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
// 将以上我们封装好的 call 返回给上层,这个时候我们就可以拿到 call,执行请求。
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});
}
返回值就是我们自定义的接口实例对象T。由于T的所有方法都是抽象方法,当调用T的方法时,会被 InvocationHandler 拦截,真正的调用会转到 InvocationHandler 的 invoke() 方法中,其中 method 参数就是我们自己的抽象方法。
invoke() 方法中,基于我们的接口方法构造了一个 ServiceMethod,构建过程中对方法中的各种注解做了解析。创建了一个 OkHttpCall 对象,这个对象将会在被adapt之后返回给客户端,类型取决于客户端的方法返回类型和设置的 CallAdapter。这里的代码其实不是很好,OkHttpCall 和 ServiceMethod 有互相引用的感觉,其实本意只是将 OkHttpCall 转换成客户端需要的返回值,那么 CallAdapter 对象是否有必要放在 ServiceMethod,我觉得可以再仔细斟酌一下。
注:此处有个预加载开关 validateEagerly ,开启后将会在调用create时就先去解析ApiService中每个方法,并且add serviceMethodCache缓存里。等到调用方法时,无需再解析,就可以直接在缓存里取解析对象了。
2. 谁去进行网络请求?我们的回调是怎么回到主线程的呢?
拿到返回的Call对象,我们可以执行网络请求了。
//4. 执行网络请求
callWorker.enqueue(new Callback<User>() {
@Override
public void onResponse(Call<BizEntity> call, Response<User> response) {...}
@Override
public void onFailure(Call<BizEntity> call, Throwable t) {...}
});
调用call.enqueue,内部实际会调用ExecutorCallAdapter的enqueue方法。
static final class ExecutorCallbackCall<T> implements Call<T> {
final Executor callbackExecutor;
final Call<T> delegate;
ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
this.callbackExecutor = callbackExecutor;
this.delegate = delegate;
}
@Override
public void enqueue(final Callback<T> callback) {
if (callback == null) throw new NullPointerException("callback == null");
// 重点关注!
delegate.enqueue(new Callback<T>() {
@Override
public void onResponse(Call<T> call, final Response<T> response) {
callbackExecutor.execute(new Runnable() {
@Override
public void run() {
if (delegate.isCanceled()) {
// Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation.
callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
} else {
callback.onResponse(ExecutorCallbackCall.this, response);
}
}
});
}
// 省略
}
这里的 delegate 对应的就是 okhttp 的 call ,我们注意到 response 的回调由callbackExecutor.execute() 来执行。一步步追踪 callbackExecutor 来源,Retrofit 的 build() 方法里:
Executor callbackExecutor = this.callbackExecutor;
if (callbackExecutor == null) {
callbackExecutor = platform.defaultCallbackExecutor();
}
平台默认的回调调度器
static class Android extends Platform {
@Override
public Executor defaultCallbackExecutor() {
return new MainThreadExecutor();
}
@Override
CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {
return new ExecutorCallAdapterFactory(callbackExecutor);
}
static class MainThreadExecutor implements Executor {
private final Handler handler = new Handler(Looper.getMainLooper());
@Override
public void execute(Runnable r) {
handler.post(r);
}
}
}
我们发现,Android 默认的调度器是主线程的 Handler ,execute()方法也只是 mainHandler.post() 。所以 enqueue() 中 onResponse 方法调用 defaultCallbackExecutor.execute 方法,实际上就是使用主线程 Handler.post(runnable) 从而实现线程切换操作。
3. 添加多个转换器和适配器时,内部优先逻辑是什么?
Retrofit mRetrofit = new Retrofit.Builder()
.client(mClient)
.baseUrl(mBaseUrl)
.addConverterFactory(FastJsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
addConverterFactory 扩展的是对返回的数据类型的自动转换,addCallAdapterFactory 扩展的是对网络工作对象 callWorker 的自动转换。就算我们不添加 CallAdapterFactory 也是能实现适配转换的,原因在于 Retrofit 的 build 函数里,会添加默认的 CallAdapterFactory。
public Retrofit build() {
...
okhttp3.Call.Factory callFactory = this.callFactory;
if (callFactory == null) {
callFactory = new OkHttpClient();//使用OkHttpClient处理网络请求
}
...
//根据当前运行平台,设置默认的callAdapterFactory
adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
...
return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
callbackExecutor, validateEagerly);
}
addConverterFactory 和 addCallAdapterFactory 都是把工厂对象添加到各自数组里保存
public CallAdapter<?, ?> nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType,
Annotation[] annotations) {
// ···
int start = adapterFactories.indexOf(skipPast) + 1;
for (int i = start, count = adapterFactories.size(); i < count; i++) {
CallAdapter<?, ?> adapter = adapterFactories.get(i).get(returnType, annotations, this);
if (adapter != null) {
return adapter;
}
}
// ···
当需要转换或适配时,就循环数组,调用每个工厂对象的方法去尝试转换,转换成功,就表明合适。其实这也是责任链的另一种表现形式。
思考
总体来说,Retrofit 在类的单一职责方面分隔的很好,OkHttpCall 类只负责网络交互,凡是和函数定义相关的,都交给ServiceMethod 类去处理,而 ServiceMethod 类对使用者不公开,因为 Retrofit 是个 门面模式也就是外观模式,所有需要扩展的都在Retrofit的建造者中实现,用户只需要简单调用门面里的方法,就能满足需求,不需要关心内部的实现。
我们尝试来分析下,在一个网络请求中,哪些是易变的,哪些是不变的,为什么 Retrofit 会这么去设计?
由于 Retrofit 提供网络访问的工作对象,又是服务于具体业务,所以可以分网络访问和具体业务两部分来分析。
-
网络访问的不变性
对于网络访问来说,不变的是一定有一个实现网络访问的对象,Retrofit 选用了自家的 OkHttpClient,为了把 Retrofit 和OkHttp 解耦合,Retrofit根据依赖倒置原则定义了自己的接口 Call 即 retrofit2.Call,并定义了操作网络请求的具体类 OkHttpCall,和okHttp3.Call仅为引用关系。 -
网络访问的易变性
对于网络访问来说,易变的是网络访问的url、请求方式(get/post等)、Http请求的Header设置与安全设置等,以及返回的数据类型。针对易变的url和请求方式,Retrofit使用了方法注解的方式,可读性良好,但这需要实现对接口函数中注解的解析,这样就有了ServiceMethod。
针对Http请求的各种设置,其实Retrofit没做什么,因为Retrofit使用的OkHttp有拦截器机制,可以应付这种变化。
针对返回的数据类型,由于目标数据类型与业务有关,是不确定的,Retrofit无法提供一个万能的转换类,所以Retrofit提供了扩展接口,允许开发者自己定义 ConverterFactory 和 Converter,去实现潜在的数据类型转换。
-
具体业务的不变性
对于具体业务来说,不变的是一定要有一个Call网络工作对象,所以Retrofit可以有一个生产对象的机制(像工厂一样) -
具体业务的易变性
对于具体业务来说,易变的就是这个Call网络工作对象的类型,不仅有CallBacl回调、可能还有Flowable工作流、或者其他潜在的对象类型。针对这种Call对象的易变性,Retrofit也是无法提供一个万能的实现类,所以也是提供了扩展解耦,允许开发者自己定义CallAdapterFactory和CallAdapter,去实现潜在的Call类型转换。
因为这种Call对象的生产需要有大量的配套代码,为了简化代码,Retrofit使用动态代理来生产这个对象。
最后,因为需要处理的方法和对象太多太复杂,需要使用建造者模式来把建造过程和使用过程分离开。
可以说 Retrofit 设计得非常精妙。最后贴张架构图,跑路!

参考:
Retrofit 分析-漂亮的解耦套路
框架源码 — 可能会有趣一点地简析学习 Retrofit
Android:手把手带你 深入读懂 Retrofit 2.0 源码
拆轮子系列 - 如何由浅入深探索 Retrofit 源码?