在项目开发中其实最常见的网络请求框架就是OkHttp+Retrofit,在上一篇老生新谈,从OkHttp原理看网络请求剖析了OkHttp的原理,今天这篇文章将从不同的角度来看Retrofit的内部实现。
读完这篇文章,您将了解到:
- 什么是动态代理模式?
- Retrofit为什么要使用动态代理?
- Retrofit如何与OkHttp相结合?
- Retrofit是如何将子线程切换到主线程?
- Retrofit弥补了OkHttp的哪些缺点?
以下源码版本为retrofit:2.9.0
什么是动态代理?
在Retrofit源码分析之前需要了解的一个比较重要的点,就是动态代理模式,它是Retrofit的核心设计的开始。
动态代理和静态代理都属于代理模式,动态代理是可以在运行期动态创建某个interface的实例,我们通过Proxy.newProxyInstance
产生的代理类,当调用接口的任何方法时,都会被InvocationHandler#invoke
方法拦截,同时,在这个方法中可以拿到所传入的参数等,依照参数值再做相应的处理。
定义比较抽象,举个普通的例子。
例如某用户近期需要购房,那么他就需要委托中介帮忙推荐房源,之后买家通过中介与开发商达成买卖协议。这里的中介就是代理对象,买家则是委托者,而其中“推荐房源”这件事情,则是抽象对象,也是委托者真正需要做的动作。
看下伪代码,首先定义委托者想要做的动作,并且告诉中介我对房子的一些基本要求:
/**
* 买家委托中介购房。
*/
interface ToDo {
fun buyHouse()
}
定义代理对象,即中介,并实现InvocationHandler,根据委托者的要求点返回代理对象:
/**
* 中介
*/
class Middlemen(private val any: Any) : InvocationHandler {
override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {
return method?.invoke(any, *(args ?: arrayOfNulls<Any>(0)))
}
}
委托者告诉中介,我要购房的信息:
/**
* 买家
*/
class Buyer : ToDo {
override fun buyHouse() {
print("中介,请帮我买房子")
}
}
最后利用代理对象实现购房的动作:
fun main(args: Array<String>) {
val toDo: ToDo = Buyer()
val dynamicMid = Proxy.newProxyInstance(
toDo.javaClass.classLoader, toDo.javaClass.interfaces,
Middlemen(toDo)
) as ToDo
dynamicMid.buyHouse()
}
动态代理最主要的部分在于代理对象实现InvocationHandler,并重写invoke方法。 当代理对象代理了委托者的要求,不管要求有多少,当代理执行时,都会走进invoke()方法中。这是重点,圈起来后面要考。
我们应该了解到,Retrofit的核心部分就在这动态代理中,那Retrofit为什么要使用动态代理?且动态代理中又做了哪些动作? ⁉️
那就接着往下分析源码。
源码解析
在源码分析之前,有两个重要的对象需要提前说明,提前了解他们的作用,更有利于后续的源码解读。
一个是CallAdapter,另外则是Converter。
- CallAdapter: 适配器,我们默认定义API Service方法的返回值为Call<T>类型,但是有时候会自定义返回类型,例如和RxJava相结合,返回Observable或者Single类型的时候应该怎么处理?CallAdapter的作用就是帮助开发者去适配这些返回类型,你定义了什么类型的数据,就可以通过CallAdapter#adapt进行返回。
- Converter: 数据装换器,主要负责把服务器返回的数据ResponseBody转化为 T 类型的对象。例如在使用retrofit进行网络请求时,我们都会先定义一个返回值的实体类,Converter就会将网络请求的返回值转换为我们所需要的类型。
CallAdapter和Converter的作用如上述所说,接下来就直接进入到retrofit的动态代理源码中。
public <T> T create(final Class<T> service) {
validateServiceInterface(service);
return (T)
Proxy.newProxyInstance(
service.getClassLoader(),
new Class<?>[] {service},
new InvocationHandler() {
private final Platform platform = Platform.get();
private final Object[] emptyArgs = new Object[0];
@Override
public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
1️⃣
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
args = args != null ? args : emptyArgs;
2️⃣
return platform.isDefaultMethod(method)
? platform.invokeDefaultMethod(method, service, proxy, args)
: loadServiceMethod(method).invoke(args);
}
});
}
retrofit的创建在于retrofit.create(ApiService::class.java)
,内部使用动态代理模式,可以看到invoke方法中一开始就有几个判断:
- 1️⃣判断当前方法是否是在接口中声明,如果不是则直接按照正常流程调用invoke;
- 2️⃣判断是否属于Android平台的默认方法,如果是则直接按照正常流程调用invoke,反之,则就到了动态代理的核心部分:
loadServiceMethod(method).invoke(args)
。
最后一行代码主要分为两步:loadServiceMethod(method)
和invoke(args)
:
loadServiceMethod(method)
这个方法的作用先提一下,它主要是将网络请求方法中的信息进行初步的处理,我们在创建api service具体接口时,会加上注解(@GET,@POST,@PUT...),参数(@Path、@Query...)等,该方法就是对接口中的注解、参数等进行解析,解析接口后又生成了一个RequestFactory请求工厂对象,并且利用这个RequestFactory对象创建了一个CallAdapter。
invoke(args)
final @Nullable ReturnT invoke(Object[] args) {
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
return adapt(call, args);
}
invoke的实现中也只有两行代码。
第一行:主要利用loadServiceMethod所创建的RequestFactory以及一些转换类作为参数生成一个OkHttpCall对象, OkHttpCall其实是对OkHttp中的realCall进行了一层包装(realCall可参考上一篇OkHttp的解析),
在Retrofit里,OkHttpCall紧密连接OkHttp,它的内部同样可以调用同步execute、 异步execute方法进行网络请求,其实真正调用的也就是OkHttp的execute和execute方法。在此同时,Retrofit中会对请求响应也做了解析。
我们先来看看OkHttpCall中网络请求的细节:
@Override
public void enqueue(final Callback<T> callback) {
Objects.requireNonNull(callback, "callback == null");
okhttp3.Call call;
synchronized (this) {
...
if (call == null && failure == null) {
try {
call = rawCall = createRawCall();
}
}
...
call.enqueue(
new okhttp3.Callback() {
@Override
public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
Response<T> response;
try {
//解析请求返回值
response = parseResponse(rawResponse);
}
...
try {
callback.onResponse(OkHttpCall.this, response);
}
@Override
public void onFailure(okhttp3.Call call, IOException e) {
callFailure(e);
}
private void callFailure(Throwable e) {
try {
callback.onFailure(OkHttpCall.this, e);
}
}
});
}
以一个异步请求为例,
内部首先调用了一个createRawCall()方法,创建rawCall,这个rawCall其实指代的就是Okhttp3的call,也就是OkHttp进行网络请求调用的一个调度器;
创建好OkHttp的call后,就开始调用enqueue进行异步请求,发现在异步请求内响应的回调属于okhttp3.Callback,所返回来的结果,也都是okhttp3.Response,到这里就可以大概知道了,retrofit的网络请求其实还是由OkHttp来实现。
okhttp3.Response这个响应不方便开发者直接使用,所以retrofit在收到结果后,又对响应结果进行新一轮的解析
response = parseResponse(rawResponse)
,以Response对象的形式返回给开发者。
另外还有最后一行: adapt(call, args); 它的内部其实是由一个适配器CallAdapted
来调用,如下:
protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
return callAdapter.adapt(call);
}
在源码解析的一开始就对callAdapter进行了定义,这里将详细说明。
adapt是一个抽象方法,里面传入了上面刚创建的OkHttpCall作为参数。adapt即adapter,适配器的意思,那我们可以猜测一下,传入OkHttpCall,是不是就是为了对OkHttpCall再进行适配的工作?但,OkHttpCall已经可以进行网络请求了,为什么还需要使用CallAdapted进行再次的适配呢? ⁉️这个问题还是先记下,抛到后面解答。
咱们继续来分析动态代理的主线条,从上面分析知道,retrofit之所以调用简便,是因为内部对@GET,@POST,@Path等注解以及请求参数进行了解析,让开发者只需要关心自己新增的请求方法是不是符合规范。
而上述动态代理最主要做的事情就是创建了CallAdapted,CallAdapted其实只是一个适配器,主要需要了解的是它适配的是什么? 是否还记得我们在上面抛出了一个关于CallAdapted的问题:那就先来看看他是如何创建的?⁉️
CallAdapted
loadServiceMethod方法其实返回的就是CallAdapted对象,那我们就直奔主题,进去瞧一瞧,
abstract class HttpServiceMethod<ResponseT, ReturnT> extends ServiceMethod<ReturnT> {
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
Retrofit retrofit, Method method, RequestFactory requestFactory) {
Type adapterType;
if (isKotlinSuspendFunction) {
Type[] parameterTypes = method.getGenericParameterTypes();
Type responseType =
Utils.getParameterLowerBound(
0, (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
// Unwrap the actual body type from Response<T>.
responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
continuationWantsResponse = true;
}
adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
} else {
adapterType = method.getGenericReturnType();
}
CallAdapter<ResponseT, ReturnT> callAdapter =
createCallAdapter(retrofit, method, adapterType, annotations);
...
Converter<ResponseBody, ResponseT> responseConverter =
createResponseConverter(retrofit, method, responseType);
okhttp3.Call.Factory callFactory = retrofit.callFactory;
if (!isKotlinSuspendFunction) {
return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
}
跟踪到最后,是在HttpServiceMethod类中的parseAnnotations中发现了CallAdapted的创建..
public CallAdapter<?, ?> nextCallAdapter(
@Nullable CallAdapter.Factory skipPast, Type returnType, Annotation[] annotations) {
int start = callAdapterFactories.indexOf(skipPast) + 1;
for (int i = start, count = callAdapterFactories.size(); i < count; i++) {
CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
if (adapter != null) {
return adapter;
}
}
CallAdapter是根据returnType和annotations的类型,从callAdapterFactories工厂中进行查找,从而返回所对应的网络请求适配器,这里returnType指的是网络请求接口里方法的返回值类型,如Call<?>、Observable<?>等。annotations则指代的是注解类型,如@GET、@POST等。
这里也提到了一个适配器工厂callAdapterFactories,不同的CallAdapter就是从这个工厂中查询出来的,有查找那就必定有添加,那适配器CallAdapter是怎么添加到工厂类中的? ⁉️callAdapterFactories这个变量是属于Retrofit类,跟踪发现是由Retrofit构造函数传入,也就是Retrofit初始化时进行了赋值。
Retrofit的初始化是由一种建造者模式来创建,在Retrofit的build()方法中,找到了适配器工厂对其适配器的添加:
public Retrofit build() {
...
Executor callbackExecutor = this.callbackExecutor;
if (callbackExecutor == null) {
callbackExecutor = platform.defaultCallbackExecutor();
}
List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
//添加适配器callAdapter
callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));
...
return new Retrofit(
callFactory,
baseUrl,
unmodifiableList(converterFactories),
unmodifiableList(callAdapterFactories),
callbackExecutor,
validateEagerly);
}
platform.defaultCallAdapterFactories
指的是Android平台的一个默认的适配器工厂,当我们不使用自定义适配器工厂时,则添加的就是这默认的工厂。这里提到了自定义适配器工厂,其实我们在使用Retrofit的时候,有时候会和RxJava结合,例如在创建Retrofit时,也会addCallAdapterFactory,将RxJava2CallAdapterFactory添加到callAdapterFactories中。
mRetrofit = new Retrofit.Builder()
.client(okHttpClient)
.baseUrl(baseUrl)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
添加调用适配器工厂的目的就是支持Call以外的服务方法返回类型,如支持Observable,Single返回类型等。 在callAdapterFactories集合器添加一个默认适配器工厂时,也附带传进去了一个参数callbackExecutor,callbackExecutor是Java8或者Android平台的一个默认线程调度器,它的作用涉及到一个线程切换的问题,也就是后续需要分析的retrofit是如何将子线程切换到主线程?⁉️
这个问题还是先记下,后面针对线程切换详细说明。
在上面源码创建CallAdapter时,有一个对象也同时被创建,那就是Converter。
Converter
Converter<ResponseBody, ResponseT> responseConverter = createResponseConverter(retrofit, method, responseType);
Converter在文章一开头也说过,是数据装换器。它的添加和获取方式和CallAdapter类似。在Retrofit初始化时添加到List<Converter.Factory>工厂集合,例如与GSon结合时,addConverterFactory(GsonConverterFactory.create()
mRetrofit = new Retrofit.Builder()
.client(mOkHttpClient)
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create()
.build();
后面也根据类型Type取出不同的Converter,对返回的网络响应,做出数据的转换,例如转换成实体类。
基本逻辑与CallAdapter类似。
说了这么多,是不是有点思路了,我们先来捋一捋。
- 一开始动态代理中调用loadServiceMethod方法,解析接口方法中的注解,参数,头部信息等;
- 依据接口方法的返回类型,从适配器工厂集合里进行查询,生成相应的适配器CallAdapter,区分是RxJava的Observable、Single还是Call或者其他类型,(适配器工厂集合的数据是由构建Retrofit时addCallAdapterFactory()添加,如无自定义,则添加Android平台默认适配器)。以相同的方式取出数据转换器Converter;
- 利用上面生成的CallAdapter,调用invoke方法,创建OkHttpCall对象,即针对请求信息,利用OkHttp进行异步或者同步网络请求,并且对响应结果进行实体类转换;
- 创建好OkHttpCall后,又利用上面查询到的适配器CallAdapter调用adapt,返回RxJava的Observable、Single或者Call对象。
在Retrofit初始化时有涉及到一个参数callbackExecutor,当时抛出了一个问题:Retrofit是如何将子线程切换到主线程?这里就来详细看看。
Retrofit是如何将子线程切换到主线程?
在添加默认适配器工厂defaultCallAdapterFactories时,将callbackExecutor作为了一个参数,那么它的具体实现也就是在这个默认适配器工厂中。
我们来看下callbackExecutor在里面做了些啥。
static final class ExecutorCallbackCall<T> implements Call<T> {
final Executor callbackExecutor;
final Call<T> delegate;
...
@Override
public void enqueue(final Callback<T> callback) {
delegate.enqueue(
new Callback<T>() {
@Override
public void onResponse(Call<T> call, final Response<T> response) {
callbackExecutor.execute(
() -> {
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);
}
});
}
@Override
public void onFailure(Call<T> call, final Throwable t) {
callbackExecutor.execute(() -> callback.onFailure(ExecutorCallbackCall.this, t));
}
});
}
在上述代码里了解到,callbackExecutor即Executor,一个线程调度器。在Call的enqueue实现里执行了一个异步网络请求delegate.enqueue,在请求的响应onResponse、onFailure中
Executor也同样执行了一个线程,这里就有个疑问,为什么要在一个异步请求里又调用一个线程?我们知道callbackExecutor是一个线程调度器,那他内部到底实现的是什么?
默认callbackExecutor的创建在Retrofit的初始化中,callbackExecutor = platform.defaultCallbackExecutor();
static final class Android extends Platform {
@Override
public Executor defaultCallbackExecutor() {
return new MainThreadExecutor();
}
static final class MainThreadExecutor implements Executor {
private final Handler handler = new Handler(Looper.getMainLooper());
@Override
public void execute(Runnable r) {
handler.post(r);
}
}
}
}
platform是一个Android平台,defaultCallbackExecutor
内部其实调用的是 new MainThreadExecutor()
,很清楚的看到, handler.post(r)
内部使用Handler将响应抛到了主线程。
这就是Retrofit将子线程切换到主线程的核心所在。
Retrofit为什么要使用动态代理?
文章一开始就抛出了这个问题,经过一轮分析后,也就有了答案。
我们想一下,动态代理的优势是什么?是不是不用暴露真实的委托者,根据不同的委托创建不同的代理,通过代理去做事情。
那Retrofit弥补了OkHttp的缺点又指的是什么?OkHttp在使用的时候,请求参数的配置是不是很繁琐,尤其当有一些表单提交时,又臭又长,而Retrofit就是弥补了这个缺点,利用@GET、@POST、@Path、@Body等注解以及一些参数很简便的就构造出了请求。
当Retrofit创建了不同的接口,动态代理就发挥出了作用。每当不同接口方法执行时,动态代理都会拦截该请求,对接口中的注解,参数进行解析,构建出不同的Request,最后则交给OkHttp去真正执行。
Retrofit结合动态代理,不用关心真正的接口方法,对符合规范的接口进行统一化的管理,以统一的方式解析注解和参数,拼接成request。
总结
通过源码以及问题的解答,了解到Retrofit其实是OkHttp的封装类,内部网络请求还是靠的OkHttp,那Retrofit封装后改变了什么?
- 接口请求更加简便,标注注解@GET、@POST、@Path、@Body等就形成一个网络请求;
- 默认帮助开发者解析responseBody,另外还可以自定义解析策略;
- Retrofit帮助开发者进行线程切换;
- Retrofit带给开发者更多的权限,可自定义适配网络请求。
Retrofit源码不多,但是却包含了很多设计技巧,后续将开一篇详细说明Retrofit的设计之美。
以上便是Retrofit的源码解析,希望这篇文章能帮到您,感谢阅读。
推荐阅读
老生新谈,从OkHttp原理看网络请求
【网络篇】开发必备知识点:UDP/TCP协议
欢迎关注公 z 号:9点大前端,每天9点推荐更多前端、Android、Flutter文章