在之前的文章 《Andriod 网络框架 OkHttp 源码解析》 中我们分析了 OkHttp 的源代码。现在我们就来分析一下 OkHttp 的兄弟框架 Retrofit。关于 Retrofit 的注解的使用,可以参考其官方文档:https://square.github.io/retrofit/。
Retrofit 也是 Square 发布的一个开源的库,它是一个类型安全的 Http 客户端,适用于 Android 和 Java。本质上,Retrofit 使用了 Java 的动态代理,内部使用 OkHttp 来进行网络访问,并且可以通过指定 “请求适配器” 和 “类型转换器” 来完成:请求的适配,方法参数到 OkHttp 请求的转换,以及响应到 Java 类型的转换。
1、基本使用
Retrofit 设计的一个好的地方就是它把我们上面提到的 “请求适配器” 和 “类型转换器” 使用策略模式解耦出来。用户可以根据自己的需求通过实现指定的接口来自定义自己的类型转换器。所以,当我们使用 Gson 进行转换和 RxJava2 进行适配的时候,就需要指定下面三个依赖:
api 'com.squareup.retrofit2:retrofit:2.4.0'
api 'com.squareup.retrofit2:converter-gson:2.4.0'
api 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
然后,我们需要根据自己的 API 接口的信息,在代码里用一个接口来对该 API 进行声明:
public interface WXInfoService {
@GET("/sns/userinfo")
Observable getWXUserInfo(
@Query("access_token") String accessToken, @Query("openid") String openId);
}
这里的 WXUserInfo
是由该 API 接口返回的 Json 生成的 Java 对象。然后,我们可以像下面这样获取一个该接口的代理对象:
WXInfoService wXInfoService = new Retrofit.Builder()
.baseUrl("https://api.weixin.qq.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.build().create(WXInfoService.class);
然后,我们就可以使用该对象并调用其方法来获取接口返回的信息了:
Disposable disposable = wxInfoService.getWXUserInfo(accessToken, openId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(wxUserInfo -> { /*...拿到结果之后进行处理...*/ });
上面我们只使用了 Retrofit 最基础的 GET
接口。当然,Retrofit 本身的功能远比这要丰富得多,关于其更多的使用,可以参考其官方的文档。
2、动态代理:魔力发生的地方
上面我们使用 Retrofit 进行网络请求,实际其内部使用 OkHttp 来完成网络请求的。仅定义了一个接口并调用了该接口的方法,就拿到了请求的结果,这看上去非常简洁,而这其中的功不可没的就是动态代理。
当我们使用 Retrofit.Builder
的 create()
方法获取一个 WXInfoService
实例的时候,实际返回的是经过代理之后的对象。该方法内部会调用 Proxy
的静态方法 newProxyInstance()
来得到一个代理之后的实例。为了说明这个方法的作用,我们写了一个例子:
public static void main(String...args) {
Service service = getProxy(Service.class);
String aJson = service.getAInfo();
System.out.println(aJson);
String bJson = service.getBInfo();
System.out.println(bJson);
}
private static T getProxy(final Class service) {
InvocationHandler h = (proxy, method, args) -> {
String json = "{}";
if (method.getName().equals("getAInfo")) {
json = "{A请求的结果}";
} else if (method.getName().equals("getBInfo")) {
json = "{B请求的结果}";
}
return json;
};
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class[]{service}, h);
}
该程序的输出结果:
{A请求的结果}
{B请求的结果}
在上面的这个例子中,我们先使用 getProxy()
获取一个代理之后的实例,然后依次调用它的 getAInfo()
和 getBInfo()
方法,来模拟调用 A 接口和 B 接口的情形,并依次得到了 A 请求的结果和 B 请求的结果。
上面的效果近似于我们使用 Retrofit 访问接口的过程。为了说明这个过程中发生了什么,我们需要先了解一下这里的 newProxyInstance()
方法:
public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) {
// ...
}
该方法接收三个参数:
- 第一个是类加载器;
- 第二个是接口的 Class 类型;
- 第三个是一个处理器,你可以将其看作一个用于回调的接口。当我们的代理实例触发了某个方法的时候,就会触发该回调接口的方法。
InvocationHandler
是一个接口,它内部定义了一个方法如下:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
该方法也接收三个参数,第一个是触发该方法的代理实例;第二个是触发的方法;第三个是触发的方法的参数。invoke()
方法的返回结果会作为代理类的方法执行的结果。
所以,当了解了 newProxyInstance()
方法的定义之后,我们可以做如下总结:当我们使用 newProxyInstance()
方法获取了一个代理实例 service
并调用其 getAInfo()
方法之后,该方法的信息和参数信息会分别通过 method
和 args
传入到 h
的 invoke()
中。所以,最终的效果就是,当我们调用 service
的 getAInfo()
时候会触发 h
的 invoke()
。在 invoke()
方法中我们根据 method
得知触发的方法是 getAInfo
。于是,我们把它对应的请求从 invoke()
方法中返回,并作为 service.getAInfo()
的返回结果。
所以,我们可以总结 Retrofit 的大致工作流程:当我们获取了接口的代理实例,并调用它的 getWXUserInfo()
方法之后,该方法的请求参数会传递到代理类的 InvocationHandler.invoke()
方法中。然后在该方法中,我们将这些信息转换成 OkHttp 的 Request
并使用 OkHttp 进行访问。从网络中拿到结果之后,我们使用 “转换器” 将响应转换成接口指定的 Java 类型。
上面是 Retrofit 请求处理的基本流程,下面我们看一下 Retrofit 的代理方法内部究竟发生了什么。
3、Retrofit 的源码解析
3.1 创建 Retrofit
根据上面的例子,当使用 Retrofit 的时候,首先我们需要使用 Retrofit 的构建者来创建 Retrofit 的实例。这里的构建者有几个重要的方法需要提及一下:
3.1.1 addConverterFactory 方法
该方法用来向 Retrofit 中添加一个 Converter.Factory
。Converter.Factory
,顾名思义是一种工厂模式。它是一个接口需要,实现两个重要的方法。每个方法需要返回一个转换器:某种数据类型到请求体的转换器,响应体到我们需要的数据类型的转换器。当我们使用 Gson 来完成这个转换,那么我们就需要使用 GsonConverterFactory.create()
来得到一个适用于 Gson 的 Converter.Factory
。
public Builder addConverterFactory(Converter.Factory factory) {
converterFactories.add(checkNotNull(factory, "factory == null"));
return this;
}
3.1.2 addCallAdapterFactory 方法
CallAdapter.Factory
用于获取 CallAdapter
对象, CallAdapter
对象用于把原生的 OkHttp 的 Call
转换成我们指定的请求类型。比如,上面的例子中,我们用来将其转换成 Observable
。下面是该方法的定义:
public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
callAdapterFactories.add(checkNotNull(factory, "factory == null"));
return this;
}
3.1.3 build 方法
当根据用户的自定义设置完了参数之后,就可以调用 build()
方法,来获取一个 Retrofit 的实例。在该方法中会将上述方法传入的 “适配器” 和 “转换器” 添加到各自的列表中,然后 new
出一个 Retrofit
的实例并返回。
3.1.4 小结
为了说明适配器 CallAdapter
和转换器 Converter
的作用,我们绘制了下图:
从上面我们看出,CallAdapter
主要用来将某个请求转换成我们指定的类型。比如,在我们最开始的例子中,要将请求转换成 Observable
。如果转换之后的请求是 Observable
类型的,那么当我们对转换后的请求进行订阅的时候,就启动了 OkHttp 的网络请求过程。
在进行网络请求之前会先使用 Converter
将请求的参数转换成一个 RequestBody
。这里将其作为一个接口的好处是便于解耦。比如,上面我们用 Gson 来完成转换过程,你也可以通过自定义转换器来使用其他的框架,比如 Moshi 等。当拿到了响应之后,我们又会再次使用 Converter
来将响应体 ResponseBody
转换成我们要求的类型。比如 WXUserInfo
。
从上面我们看出,Retrofit 设计的非常妙的地方就在于上面的两个过程的解耦(策略模式+工厂模式+适配器模式)。一次是将请求转换成 Observable
的过程,一次是将请求体和响应体转换成 OkHttp 要求的 RequestBody
和 ResponseBody
的过程。对于前者,不论我们使用的是 RxJava 1 还是 RxJava 2,只要传入一个 CallAdapter
即可。对于后者,不论我们使用哪种 Json 转换框架,只要实现了 Converter
接口皆可。
3.2 获取代理实例
3.2.1 划分平台:Platform
创建了 Retrofit 的实例之后,我们就可以使用它的 create()
方法来获取代理之后的服务实例。下面是这个方法的定义。在这里,我们会先根据 validateEagerly
变量来判断是否立即对传入的服务接口的方法进行解析。然后,我们使用 Proxy
的静态方法获取一个代理实例。
public T create(final Class service) {
Utils.validateServiceInterface(service);
// 这里的 validateEagerly 在 Retrofit 构建的时候设置
if (validateEagerly) {
// 是否立即对 Service 方法的内容进行解析
eagerlyValidateMethods(service);
}
// 获取代理实例
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
@Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
// 该方法是 Object 的方法,直接触发该方法
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
// 如果是 default 方法,那么使用该 Java8 平台的方法执行
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
// 获取服务方法的信息,并将其包装成 ServiceMethod
ServiceMethod