您的位置:首页 > 其它

解锁Retrofit -- 浅析Retrofit源码

2017-12-13 21:48 274 查看

前言

在Android的异步网络请求中,一般都会使用到一些优秀的开源库来简化网络请求与线程切换过程,Retrofit+RxJava也是时下最热门的搭配,对与Retrofit的学习,也给我带来的极大的收获,特别是了解其中的一些套路,会给自己的开发带来很大的启示。

1.主线任务

在具体分析之前,采用单步调试的方式先把主线拎出来,理解大致的运行过程



在开始分析之前,有两个需要明白的地方:

1.Retrofit只能算是一个加强版的API管理库,具体的网络请求是依赖内部的OkHttp来实现的。

2. Retrofit是通过动态代理解析注解来构建Request发起请求的。

从图中可以看到:

1.在发起请求的时候,Retrofit会尝试去加载一个ServiceMethod,这个ServiceMethod的作用是封装了Retrofit的三大核心功能:CallAdapter、Converter和从注解解析出的Request(具体过程后面会提到)。当然,由于每次调用ApiService接口中的方法时都会通过动态代理进行拦截,然后执行上图的一整套过程,所以为了避免每次都去花费过多的时间去创建ServiceMathod,就需要将它缓存到一个Map中,以减少开销。

2.在拿到ServiceMethod过后,需要将它交给OkHttpCall,它其实就相当于是一个OkHttp的包装类,可以用来执行具体的请求,这里只是将ServiceMethod和接口方法中的参数传了进去,给OkHttpCall的成员变量赋值,暂时没有做什么具体的工作。

3.接下来就是将OkHttpCall交给CallAdapter进行适配的,这里就要分情况进行分析了,如果指定了常用的RxJavaCallAdapter,自然就会将OkHttpCall转化为Observable类型的Call;如果没有制定具体的CallAdapter,则会将使用默认的Call(也就是ExecutorCallbackCall,后面会提到);当然,这里完全可以由开发者更具业务需求,自定义一个CallAdapter,这也是我们可以扩展的地方。

4.经过上一步的操作,用于请求的Call也得到了,就该使用OkHttpClient来执行真正的网络请求了。

5.在请求执行完,服务器返回结果之后就会使用Converter来讲结果转换为我们需要的数据格式。

6.最终,采用RxJava或者直接使用Handler切换到到主线程,数据也在这个时候回调到了主线程。

2.理解三个大的模块的逻辑

我认为学习Retrofit主要需要从三个大的方向去入手:Request的构建方式、CallAdapter的创建或注入时机、Converter的创建或注入时机。下面就分别跟着源码来进行一番探索:

2.1 使用动态代理+注解+反射的方式来构建Request

在第一次使用Retrofit的时候,肯定会被这种配置请求方式的形式吸引到,没想到还有注解这种操作..

以常用的GET方式为例,来看看究竟是怎么将这些注解和接口方法转化为请求需要的Call的。

public interface ApiService {
@GET("top250")
Call<MovieBeen> getMovie(@Query("start") int start, @Query("count") int count);
}


在配置Request信息的时候只需要在在接口中使用注解即可,使用的时候直接调用:

ApiService apiService = retrofit.create(ApiService.class);


这一句话就可以拿到ApiServerce的实例,往往外层简单的东西,内层都不简单,进入create() 方法一探究竟:

public <T> T create(final Class<T> service) {
···
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
···
ServiceMethod<Object, Object> serviceMethod =
(ServiceMethod<Object, Object>) loadServiceMethod(method);
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});
}


仔细一看,这里直接返回了一个动态代理的代理对象,回想一下动态代理的作用,拦截接口中的方法,在方法调用前后加上自己的逻辑,而这里的主要操作是通过动态代理创建了一个
ServiceMethod
OkHttpCall


先去看看
ServiceMethod
是个什么东西,这个类的注释写得很精辟:“Adapts an invocation of an interface method into an HTTP call(将一个接口中方法的调用时配成一个HTTP的Call)”。(因为Retrofit内部是基于OkHttpCall来完成具体的请求的,所以对于Call这个类应该很好理解,有了它,便可以发起网络请求。)

跟进
loadServiceMethod()
方法:

ServiceMethod<?, ?> loadServiceMethod(Method method) {
//现尝试从缓存中取出ServiceMethod,如果取到了就直接返回
ServiceMethod<?, ?> result = serviceMethodCache.get(method);
if (result != null) return result;

synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
//缓存中没有的话,就构造一个ServiceMethod,再加入缓存
result = new ServiceMethod.Builder<>(this, method).build();
serviceMethodCache.put(method, result);
}
}
return result;
}


这里逻辑还是很简单,就是对ServiceMethod进行了一个缓存的操作,不然每次调用ApiServerce中的方法的时候都会创建一个ServiceMethod的实例,势必会带来太大的性能开销。

这里采用了建造者模式的思想来构建ServiceMethod的实例,所以直接进入build() 方法,可以看到便可以看到解析通过反射拿到的注解的方法–
parseMethodAnnotation()


public ServiceMethod build() {
···
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}

···

return new ServiceMethod<>(this);
}


进入
parseMethodAnnotation()
方法查看一番:

private void parseMethodAnnotation(Annotation annotation) {
if (annotation instanceof DELETE) {
···
} else if (annotation instanceof GET) {
parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
} else if   ···
}


一大波
if - else
来临,这里通过对请求方法中注解的请求方式的判断(这里以“GET”为例),从而调用
parseHttpMethodAndPath()
来解析出之前配置的URL信息:

private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
···
this.httpMethod = httpMethod;
this.hasBody = hasBody;

···

this.relativeUrl = value;
this.relativeUrlParamNames = parsePathParameters(value);
}


比如对应于本文开头的这样一个接口方法:

@GET("top250")
Call<MovieBeen> getMovie(@Query("start") int start, @Query("count") int count);


这个时候parseHttpMethodAndPath()的形参的值分别是:(“GET”,”top250”,false),接下来就是真正的解析工作了。在解析完方法的注解后,就该解析方法的参数的注解了,回到build()方法中可以看到:

public ServiceMethod build() {
···
int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];
for (int p = 0; p < parameterCount; p++) {
Type parameterType = parameterTypes[p];
···
Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
···

parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
}
···

return new ServiceMethod<>(this);
}


parseParameter()
方法就是用来解析参数注解的,具体的解析过程就不深入了,只要知道build()方法里面将会完成我们所配置的注解的解析。

总结一下,简单跟踪了一下源码就发现了Retrofit是采用动态代理的方式拦截接口中的方法,然后通过反射拿到方法注解,最后解析注解,使用这种方式的好处是可以给上层提供一种方便的方式配置请求的API,所以在我看来,Retrofit本质上是一个管理API的库,真正的网络请求部分是交给OkHTTP库来完成的。

2.2 添加CallAdapter转换OkHttpCall

Retrofit的一大优势就是添加了对RxJava的支持,也就是可以再构造Retrofit的时候添加一个RxJava2CallAdapterFactory,这大大增强了Retrofit的战斗力,那么要是没有添加其他的CallAdapter,它又是如何工作的呢?肯定会有一个默认的CallAdapter,跟着源码来查看一番:

在Retrofit#create()方法里可以看到,动态代理最后返回的是:
serviceMethod.callAdapter.adapt(okHttpCall)


public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {

b3da
eagerlyValidateMethods(service);
}
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
···
ServiceMethod<Object, Object> serviceMethod =
(ServiceMethod<Object, Object>) loadServiceMethod(method);
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});
}


跟踪进去最终便可以跟到:

ExecutorCallAdapterFactory#get()
,最终返回的是一个
ExecutorCallbackCall


@Override
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
if (getRawType(returnType) != Call.class) {
return null;
}
final Type responseType = Utils.getCallResponseType(returnType);
return new CallAdapter<Object, Call<?>>() {
@Override public Type responseType() {
return responseType;
}

@Override public Call<Object> adapt(Call<Object> call) {
return new ExecutorCallbackCall<>(callbackExecutor, call);
}
};
}


这便是默认的的Call了,默认情况下最后也是将它交给了OkHttpClient完成请求。

这里Call完全可以由开发者根据自己的项目的业务逻辑来做具体的开发,灵活性也比较高。

2.3 提供灵活的API来设置Converter

回忆一下,在没有Gson等反序列化的开源库的时候,往往需要用JsonObject和JsonArray来完成反序列化操作,当出现Gson这类开源库的时候,反序列化的步骤优雅了很多,从Json转化到JavaBean的过程我们也不需要花过多的时间关注了,在构建Retrofit的时候提供了一个
addConverterFactory()
方法来添加一个转换器,这里也支持多种官方提供的转换器:

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


那么转化器是在什么时候构造的?其实Converter也是在ServiceMethod中创建的,跟着代码来分析一下:

public ServiceMethod build() {
···
responseConverter = createResponseConverter();
···

}


其实build()方法很显眼的位置就有一个createResponseConverter()方法,来创建Converter:

private Converter<ResponseBody, T> createResponseConverter() {
Annotation[] annotations = method.getAnnotations();
···
return retrofit.responseBodyConverter(responseType, annotations);
···
}


在这个方法里面也会先拿到方法的注解,再将拿到的注解和Response的类型传入responseBodyConverter()方法,进行转换操作,最便可将服务器返回的流文件反序列化成我们需要的内容。

其实这里使用官方提供的几种Converter也差不多能满足日常开发的需求的,基本不需要怎么扩展。

到这里对于整个Retrofit的简要分析也完成,最后来说几点自己的愚见吧:

1.Retrofit的在实现缓存策略的时候是将CallAdapter,Converter和解析注解的模块都封装进了ServiceMethod,然而在代码里面可以看到OkHttpCall的构建是需要传入ServiceMethod的,再将OkHttpCall适配成其他的Call的时候,又需要将OkHttpCall传入ServiceMethod,这样的操作貌似看起来并不是很优雅,如果单独单独缓存CallAdapter应该会优雅一点。

OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);

return serviceMethod.callAdapter.adapt(okHttpCall);


但其实也并不影响使用,CallAdapter还是可以灵活替换的。

2.在Retrofit底层将完成网络请求的操作限定死了,只能用OkHttp,要是以后出现了更优秀的网络请求库,可能要替换也有些麻烦。(当然都是自己家的东西,估计近几年也不会有什么可替换的)。

3.对于这么优秀的库还有很多细节没有去深究,等以后遇到具体的需求之后去研究起来肯定又有更多的体会了。

4.在Rtrofit中运用了许多有用的设计模式,如:动态代理,Buider模式,工厂模式,对于学习的价值还是很大的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: