研究 retrofit 目标:理解动态代理、注解、反射、学习它所用到的设计模式,达到自己能手写它的核心实现。

最近终于有点精力能够去研究研究源码了, 真的是写的一个非常好的的开源库,以前刚接触安卓的时候扒拉过相关的源码,但是随着工作了几年之后,经验的积累,让我对源码里面的东西能够体会更深刻,自己也尝试去手写里面的核心实现,看完源码对整体的架构理解了之后,以为自己能很顺利的写下来,实则不然。
知识还是需要知行合一,这篇文章主要记录 retrofit 的一些知识点。

retrofit 的设计模式

retrofit 里面中使用了多种设计模式,以实现其灵活、可扩展和高性能的特性:

设计模式 应用场景 作用
建造者模式 Retrofit.Builder 灵活配置 Retrofit 实例
工厂模式 Converter.FactoryCallAdapter.Factory 创建 Converter 和 CallAdapter 实例
动态代理模式 接口方法转换为 HTTP 请求 运行时生成接口代理对象
适配器模式 CallAdapter Call 适配为其他类型
装饰器模式 OkHttp 拦截器 增强 HTTP 请求和响应的功能
观察者模式 RxJavaLiveData 结合 实现异步数据流的订阅和通知
策略模式 ConverterCallAdapter 选择 动态选择数据转换或调用适配策略
单例模式 Retrofit 实例共享 确保全局只有一个 Retrofit 实例
模板方法模式 Call 的实现 定义 HTTP 请求的执行流程

retrofit 的动态代理模式

retrofit 用了诸多的设计模式,其中最经典的莫过于动态代理模式了,在了解 retrofit 之前,我一直以为这样的网络请求形式是最直观的,参考以前写的基于Volley框架的返回数据的范型处理

Request.get(
url = url,
params = param,
listener = object : OnRequestListener<Data> {
override fun onSuccess(commonData: CommonData?, data: Data?) {}
override fun onFailure(errorCode: Int, errorMessage: String?) {}
})

以为这样很直观,逻辑也很清晰,实则 代码冗余,回调嵌套,如果有多个连续的请求,代码会变得难以维护,而 retrofit 搭配上协程能这样实现:

val data = apiService.getXXX(params)

简单到不能再简单,动态代理功不可没,上面的 apiService 是一个接口,由: retrofit.create(ApiInterface::class.java) 生成其实例,动态代理其核心实现:

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 {
// ....
return platform.isDefaultMethod(method)
? platform.invokeDefaultMethod(method, service, proxy, args)
: loadServiceMethod(method).invoke(args);
}
});
}

loadServiceMethod(method).invoke(args) 负责将接口方法(通过 Java 反射获取的 Method 对象)解析并转换为一个可执行的 HTTP 请求。

Proxy.newProxyInstance 方法,参数:

  • ClassLoader loader 用于加载代理类的类加载器。

  • Class<?>[] interfaces 代理类需要实现的接口数组,代理对象将实现这些接口,并拦截对这些接口方法的调用。只能代理实现了接口的类,不能代理没有接口的类。

  • InvocationHandler h
    调用处理器,负责处理代理对象上的方法调用。每次调用代理对象的方法时,都会调用 InvocationHandlerinvoke 方法。对于 Retrofit 的接口我们并没有去“实现”它的方法,所有的逻辑都由`` retrofit.create()方法里面返回的 InvocationHandler实现的 invoke`方法实现的。

核心实现逻辑

协程的支持

Retrofit 支持多种异步编程模型,包括回调、RxJava 和协程等,这里主要记录一下对协程的支持。普通方法和异步逻辑的分叉在:

if (!isKotlinSuspendFunction) {
return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
} else if (continuationWantsResponse) {
//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
return (HttpServiceMethod<ResponseT, ReturnT>)
new SuspendForResponse<>(
requestFactory,
callFactory,
responseConverter,
(CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
} else {
//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
//...
}

上面代码关键变量isKotlinSuspendFunction ,用于判断是否为协程方法(suspend修饰),判断逻辑很简单,只需要判定方法最后一个参数是否为 Continuation.class 即可。这里的分叉逻辑都继承自HttpServiceMethod<T>实现 ReturnT adapt(Call<ResponseT> call, Object[] args)这个抽象方法,这也是 retrofit 使用 适配器模式的地方,把不同的调用方式进行统一。对于协程方式的调用有实现:

protected Object adapt(Call<ResponseT> call, Object[] args) {
call = callAdapter.adapt(call);

//noinspection unchecked Checked by reflection inside RequestFactory.
Continuation<Response<ResponseT>> continuation =
(Continuation<Response<ResponseT>>) args[args.length - 1];

// See SuspendForBody for explanation about this try/catch.
try {
return KotlinExtensions.awaitResponse(call, continuation);
} catch (Exception e) {
return.suspendAndThrow(e, continuation);
}
}
suspend fun <T> Call<T>.awaitResponse(): Response<T> {
return suspendCancellableCoroutine { continuation ->
continuation.invokeOnCancellation {
cancel()
}
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
continuation.resume(response)
}

override fun onFailure(call: Call<T>, t: Throwable) {
continuation.resumeWithException(t)
}
})
}
}

这里就一切都明朗了,实现了 Call的扩展方法,这里的 Call并不是 okhttp3.Call,它只是 retrofit okhttp3.Call为方便框架整体逻辑的处理而定义的,比如 retrofit 的 Call 是泛型化的,可以直接返回解析后的对象,enqueue同理。

suspendCancellableCoroutine方法是实现协程方法的关键,它可以将基于回调的异步操作封装成一个挂起函数,怎么理解呢?对 扩展方法awaitResponse反编译可以看到方法定义是这样的:

public static final Object await(@NotNull Call $this$await, @NotNull Continuation $completion) 

其实这里跟定义一个 listener去监听方法的回调有点像,这个方法改写成 listener的实现话大概就是这样:

fun <T> Call<T>.awaitResponse(listener:Listener<T>): Response<T> {
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
Listener.resume(response)
}

override fun onFailure(call: Call<T>, t: Throwable) {
Listener.resumeWithException(t)
}
})
}
}

可看到改造实现需要传递一个 listener,哪这个 listener是什么?前面其有如何判断一个方法是否为协程的方法的逻辑:判定方法最后一个参数是否为 Continuation.class 即可。这里的 listener 其实可以等价于 一个 Continuation实例,kotlin 的协程库帮我们实现了对应的封装,对于使用我们不会直观地感受Continuation的存在,实际它贯穿整个协程。关于协程这里不再赘述,可以查看 《带着问题分析Kotlin协程原理》了解。

返回数据格式的解析

对于Converter,在协程和普通方法调用分叉逻辑的前面点:

Converter<ResponseBody, ResponseT> responseConverter = createResponseConverter(retrofit, method, responseType);

createResponseConverter之后一路走到

public <T> Converter<ResponseBody, T> nextResponseBodyConverter(
@Nullable Converter.Factory skipPast, Type type, Annotation[] annotations) {
Objects.requireNonNull(type, "type == null");
Objects.requireNonNull(annotations, "annotations == null");

int start = converterFactories.indexOf(skipPast) + 1;
for (int i = start, count = converterFactories.size(); i < count; i++) {
Converter<ResponseBody, ?> converter =
converterFactories.get(i).responseBodyConverter(type, annotations, this);
if (converter != null) {
//noinspection unchecked
return (Converter<ResponseBody, T>) converter;
}
}
}

converterFactories 的值就是在 retrofit 初始化的时候进行使用 public Builder addConverterFactory(Converter.Factory factory)添加的值。可以看到是按添加到List<Converter.Factory> converterFactories里面的顺序进行选择的,默认GsonConverterFactory实现了利用 Gson进行数据转化 ,如果我们自己实现Converter.Factory的接口的话,那么可以根据一定的规则判断是否要返回我们自定义的 Converter,如果不需要使用就返回 null,会自动匹配下一个能使用的 Converter。注意这里并不会因为前一个 Converter 解析失败而自动尝试使用下一个Converter(当然,你可以在自定义的Converter里面做类似这样的尝试策略)。

总结

  • 这篇文章深入剖析了 Retrofit 框架的核心设计模式、动态代理机制、协程支持以及数据解析逻辑,通过源码分析和手写实现,帮助读者更好地理解 Retrofit 的工作原理,并强调了理论与实践结合的重要性。

  • 为加深对 retrofit 的理解,可以尝试手写核心实现,自己尝试的的代码在 vmfit

  • 附一张 retrofit 的全流程图,来源:https://cloud.tencent.com/developer/article/1683334

img