对于 Retrofit 应该是再熟悉不过了,都知道它是一个网络框架,但是为什么它还要基于 OkHttp 呢?了解 Retrofit 后会发现,它虽是网络框架却不做任何网络请求,网络请求任务都是交给 OkHttp 去执行的。Retrofit 使用动态代理、建造者、工厂、外观、适配器等设计模式,实现了自身的高度解耦以及在实际在网络请求中的高扩展性,例如多种类型 CallAdapter 和 Converter 支持。先来看看 Retrofit 的基本使用:
1. 使用步骤
-
创建请求的接口类 IService 和请求方法
设置请求方法类型,动态设置请求的 url、参数public interface IService { @GET("JakeWharton") Call
getResult(); } -
创建 Retrofit
Retrofit RETROFIT_CLIENT = new Retrofit.Builder() .baseUrl("https://github.com/") .client(OKHttpHolder.OK_HTTP_CLIENT) .addConverterFactory(GsonConverterFactory.create()) //数据转换器 .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //Retrofit配合RxJava使用 .build();
-
创建请求方法接口类(代理类)对象
IService service = retrofit.create(IService.class);
-
创建 Call
Call call1 = service.get(); Call call2 = service.post();
-
执行请求(同步/异步)
call.enqueue()/call.execute()
-
处理请求响应 Callback
call.enqueue(new Callback
() { @Override public void onResponse(@NonNull Call call, @NonNull Response response) { //请求成功 } @Override public void onFailure(@NonNull Call call, @NonNull Throwable t) { //请求失败 } });
2. 源码解析
2.1 Retrofit.Builder
Retrofit 的创建使用了建造者模式,调用 Retorfit.Builder() 时,首先会找到一个 Platform 即代码的执行平台,例如 Android平台、Java平台。
-
Retrofit.Bulider() 构造方法
public Builder() { this(Platform.get()); }
-
Platform.get()
findPlatform() 方法中实现了平台创建,由于我们是在 Android 平台使用 Retorfit,直接来看看 Android 平台做了些什么处理:
private static final Platform PLATFORM = findPlatform(); static Platform get() { return PLATFORM; } private static Platform findPlatform() { try { Class.forName("android.os.Build"); if (Build.VERSION.SDK_INT != 0) { return new Android(); } } catch (ClassNotFoundException ignored) { } try { Class.forName("java.util.Optional"); return new Java8(); } catch (ClassNotFoundException ignored) { } return new Platform(); }
-
Android 平台
有一个内部类
MainThreadExecutor
,创建了一个主线程的Handler
,ececute()
方法中通过 handler 发送消息,用于将请求的回调切回主线程,在主线程处理响应的数据。static class Android extends Platform { @Override public Executor defaultCallbackExecutor() { return new MainThreadExecutor(); } @Override CallAdapter.Factory defaultCallAdapterFactory(@Nullable Executor callbackExecutor) { if (callbackExecutor == null) throw new AssertionError(); 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); } } }
-
baseUrl(String baseUrl)
这里的 baseUrl 是我们的主机地址,通常是使用
绝对路径
,例如请求地址为:https://github.com/square/retrofit
,那么 baseUrl 即为https://github.com/
,注意 baseUrl 必须以 / 结尾。 -
client()
添加一个
OkHttpClient
,默认是一个不带任何配置的 OkHttpClient,如果我们想自定义 OkHttpClient 的连接时长、读/写时长以及拦截器时,可以通过 client() 传入我们自定义的 OkHttpClient。 -
addConverterFactory()
用于添加数据的转换器工厂,实现对象的序列化和反序列化,可以是基本数据类型的转换器工厂,也可以是
Gson、FastJson
等;例如GsonConverterFactory
,请求时 GsonRequestBodyConverter 将我们自己定义 JavaBean 或者一些基本的数据类型写入 Gson 中并转换成请求体RequestBody
发送给服务器,响应返回时 GsonResponseBodyConverter 首先将响应体ResponseBody
转换为 Gson,Gson 再转换为我们需要的 JavaBean 类型,省去了我们自己对数据的解析便于直接提取数据。 -
addCallAdapterFactory()
用于添加请求适配器工厂,默认请求适配默认使用
OkHttpCall
,也可以是RxJava
的 Observer 或者 Flowable(都是通过请求适配器将 OkHttpCall 转换得来的),Retrofit 中有三种请求适配器工厂类:-
DefaultCallAdapterFactory
-
ExecutorCallAdapterFactory( 默认的)
将
OkHttpCall
转换为ExecutorCallbackCall
,ExecutorCallbackCall 是 DefaultCallAdapterFactory 的内部类实现了 retrofit2.Call 接口; -
RxJava2CallAdapterFactory
创建 RxJava2CallAdapter ,将
OkHttpCall
转换为Observer
或者 Flowable;
@Override public Object adapt(Call
call) { ... Observable> observable; if (isFlowable) { return observable.toFlowable(BackpressureStrategy.LATEST); } ... return observable; } CallAdapter 的
adapt()
方法的作用是将默认的 OkHttpCall 转换为我们指定的请求类型,比如RxJava
的Observer
类型。 -
-
build()
2.2 Retrofit
-
create()
create() 用于动态返回请求接口类的代理类对象,代理类对象调用接口类中的方法时会调用代理类对象的 InvocationHandler 的 invoke() 方法,invoke() 方法的参数 method 即调用的接口方法,数组 args 即接口方法的参数,在这里通过反射
我们的接口方法去调用 HTTP 请求方法,反射是在 ServiceMethod 中实现。
其中Proxy.newProxyInstance() 有三个参数:-
service.getClassLoader()
:service 的 ClassLoader,service 就是我们调用 create() 传进来的自定义的接口类; -
new Class>[] { service }
:代理类要实现的接口列表,即我们在自定义的接口类中的接口方法; -
new InvocationHandler(){}
:代理类要执行的调用程序,在代理类对象调用接口方法时执行;
-
public T create(final Class service) {
...//省略内容主要用于检测 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 {
...
ServiceMethod
重点来看 loadServiceMethod() 方法:
-
loadServiceMethod()
loadServiceMethod() 用于返回一个ServieMethod
实例;- 先从缓存
serviceMethodCache
中获取一个 ServiceMethod,serviceMethodCache 是一个 Map,其中 key 为 Method,value 为 ServiceMethod,?>,即一个接口方法对应一个 ServiceMethod; - 如果获取到的 result 不为空直接返回,为空则创建一个新的 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;
}
为什么有一个 serviceMethodCache
缓存
我们的 ServiceMethod?
我们都知道反射
机制会影响程序执行的效率,而 Retrofit 将一次请求的结果(ServiceMethod)进行缓存,当再次调用同一个请求时,我们就不需要去进行反射获取 HTTP 方法和参数等信息,而是直接使用 ServiceMethod 里上一次请求已经保存的相关请求信息进行一次请求,所以当我们频繁调用同一个请求时,这里的缓存就可以在很大程度上减少反射带来的性能开销。
2.3 ServiceMethod
ServiceMethod 的作用是解析注解,通过反射
机制将请求接口类的接口方法调整为 HTTP 调用,调整的依据就是接口方法和方法参数的 注解
,所有和请求接口相关的参数、参数类型、回调类型、返回数据类型、请求适配器、数据转换器都保存在 ServiceMethod 中,同时 ServiceMethod 还实现了请求适配和数据转换器的调用,并向 OkHttpCall 提供执行 OkHttp 请求的 okhttp3.Call。
先来看ServiceMethod 内部类 Builder 的构造方法:
-
Builder
首先保存 Retrofit 和 Method 的对象,接着获取了 Method 的 methodAnnotations 、parameterTypes 、parameterAnnotationsArray 并保存,这三者分别代表方法的注解、方法的参数类型以及方法参数的注解,其中方法的注解和方法参数的注解会在 build() 方法中进行解析。
Builder(Retrofit retrofit, Method method) {
this.retrofit = retrofit;
this.method = method;
this.methodAnnotations = method.getAnnotations();
this.parameterTypes = method.getGenericParameterTypes();
this.parameterAnnotationsArray = method.getParameterAnnotations();
}
-
Builder.build()
build() 方法中首先创建了CallAdapter
对象和ResponseConverter
对象,然后将我们在 Builder 构造方法里获取到的 methodAnnotations 、parameterAnnotationsArray 通过分别调用 parseMethodAnnotation()、parseParameterAnnotation() 方法对方法的注解和方法参数的注解进行解析,代码中省略的部分都是对各个对象类型合理性的判断,如果不符合规定则抛出异常。
public ServiceMethod build() {
callAdapter = createCallAdapter();
responseType = callAdapter.responseType();
...//检测 responseType 不能是 Response.class 和 okhttp3.Response.class,否则抛出异常
responseConverter = createResponseConverter();
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}
...//检测方法的注解是否正确使用
int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler>[parameterCount];
for (int p = 0; p < parameterCount; p++) {
Type parameterType = parameterTypes[p];
...//检测参数类型是否正确
Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
if (parameterAnnotations == null) {
throw parameterError(p, "No Retrofit annotation found.");
}
parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
}
...//检测方法参数的注解是否合理
return new ServiceMethod<>(this);
}
-
parseMethodAnnotation()
- 根据请求方法的不同注解调用 parseHttpMethodAndPath() 方法,获取相对 url 路径;
- 如果方法注解中有
retrofit2.http.Headers
调用 parseHeaders() 方法解析 headers; - 方法的注解
Multipart
和FormUrlEncoded
不能共存,即一个方法的注解中只能有 @Multipart 或 @FormUrlEncoded,共存则抛出异常;
-
parseParameterAnnotation()
根据不同的方法参数注解通过
ParameterHandler
返回不同的参数处理类并保存在 parameterHandlers 数组中,每一个参数对应一个 ParameterHandler; -
toCall()
用于获取 OkHttp 请求的 Call 对象,实现 OkHttp 请求:
- 创建 RequestBuilder 对象;
- 遍历 parameterHandlers 数组,根据每个参数注解类型的不同依次将参数添加到 RequestBuilder 中;
- newCall(),创建一个新的请求调用并返回;
okhttp3.Call toCall(@Nullable Object... args) throws IOException {
RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
contentType, hasBody, isFormEncoded, isMultipart); //1
@SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
ParameterHandler[] handlers = (ParameterHandler[]) parameterHandlers;
int argumentCount = args != null ? args.length : 0;
...
for (int p = 0; p < argumentCount; p++) {//2
handlers[p].apply(requestBuilder, args[p]);
}
return callFactory.newCall(requestBuilder.build()); //3
}
-
toResponse()
解析响应,将响应体 ResponseBody 传入数据转换器 Convert 的 convert() 方法解析响应(转换为接口类方法中指定的 Java 对象),convert() 方法根据参数类型的不同将 ResponseBody 转换为对应数据类型的数据,Retrofit 中默认实现该响应数据转换的是BuiltInConverters
,我们在前面使用 Retrofit.Builder.addConvertFactory() 方法添加的转换器工厂类就是用于为这里提供数据转换器的。
R toResponse(ResponseBody body) throws IOException {
return responseConverter.convert(body);
}
-
adapt()
调用 CallAdapter 的 adapt() 方法,将 OkHttp 请求 Call 转换为我们设置的适配器类型,即我们在 Retrofit.Builder.addCallAdapterFactory() 方法中添加的适配器工厂类,默认是 Call,还可以是 RxJava。
T adapt(Call call) {
return callAdapter.adapt(call);
}
2.4 OkHttpCall
Retrofit本身用一个 OkHttpCall
类负责处理网络请求,而我们在接口中定义需要定义很多种 Call,例如 CallCallAdapter
去做一个适配转换。
Retrofit底层虽然使用了 OkHttpClient 去处理网络请求,但它并没有使用 okhttp3.Call 这个 Call 接口,而是自己又建了一个 retrofit2.Call 接口,
OkHttpCall
继承的是 retrofit2.Call,与okhttp3.Call
只是引用关系
,即在 OkHttpCall 内部创建了 okhttp3.Call。
这样的设计符合依赖倒置原则
,可以尽可能的与OkHttpClient解耦。
-
enqueue()
异步请求,使用 okhttp3.Call 执行异步请求,在 okhttp3.Call 的请求回调中调用 parseResponse() 将
okhttp3.Response
转换为retrofit2.Response
,然后将这个 Response 传给 Retrofit 的 Callback,回调里调用了 ExecutorCallbackCall 的 execute(),通过主线程的 Handler 发送消息 post(runnable) 将回调切换到了主线程处理; -
execute()
同步请求,和异步请求一样,都是通过 okhttp3.Call 执行同步请求,接着调用 parseResponse() 将
okhttp3.Response
转换为retrofit2.Response
,由于同步请求没有在子线程中执行,不存在线程切换问题,直接返回结果。 -
createRawCall()
创建一个 OkHttp 的 Call,即
okhttp3.Call
,实际上是调用 ServiceMethod 的 toCall()` 创建而来的,由此可见 Retrofit 在整个请求流程中的分工是很明确的,OkHttpCall 就是和 OkHttp 建立连接的桥梁,它的任务就只是进行网络请求和响应回调处理。 -
parseResponse()
将
okhttp3.Response
转换为 Retrofit 中的Response
,而要转换成我们想要的数据类型则需要通过Converter
进行转换。
3. 总结
简单来说 Retrofit 虽然是网络框架,但它其实并没有做任何和网络请求相关的操作,这些都是交给 OkHttp 去完成的,而对于 Retrofit 而言,它被设计出来一定有目的的。
- 在实际业务中,我们的请求参数、请求方法、Call类型、数据转换器都可能不同,Retrofit 通过注解标注接口方法和接口方法参数,通过动态代理生成接口类对象,通过工厂模式支持多种类型的 Call 和 数据转换器,这在一定程度上满足了业务需求的多样性需求;
- 我们发现,在使用 Retrofit 时代码是极其简洁的,这得益于 Retrofit 使用了建造者模式创建 Retrofit,使用外观模式去实现内部 ServiceMethod 和 OkHttpCall 的创建和使用;
- 最后会发现,其实 Retrofit 主要的类就三个,而且它们分工明确,Retrofit 用于保存我们的 CallAdapter 和 Converter 工厂以及 OkHttpClient,ServiceMethod 用于解析方法注解和方法参数并转换为 HTTP 对应的请求方法,它保存了和请求有关的所有信息,最后 OkHttpCall 用于真正的网络请求和回调处理。
最后以一张 Retrofit 的整体流程图结束这次探索之旅:
4. 参考
- 浅析 Retrofit 基本思想
- 以 API 使用的角度来解析 Retrofit
- Retrofit分析-漂亮的解耦套路