Retrofit 如何将 Suspend 函数转换为挂起请求
本文将深入 Retrofit 源码,揭示它如何识别和处理 suspend 函数,如何将传统的回调模式转换为协程友好的挂起函数,以及这一切如何在运行时无缝运作。
Retrofit 识别 Suspend 函数的机制
1. 入口:动态代理创建服务
// Retrifit.class
public <T> T create(final Class<T> service) {
validateServiceInterface(service);
// 创建动态代理实例
return (T) Proxy.newProxyInstance(
service.getClassLoader(),
new Class<?>[] {service},
new InvocationHandler() {
@Override
public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) {
// 调用实际的服务方法
return loadServiceMethod(service, method).invoke(proxy, args);
}
});
}
2. 关键检测:发现 Continuation 参数
// RequestFactory.class - 解析方法参数
private @Nullable ParameterHandler<?> parseParameter(
int p, Type parameterType,
@Nullable Annotation[] annotations,
boolean allowContinuation) {
// 检测是否为 Continuation 类型参数
if (Utils.getRawType(parameterType) == Continuation.class) {
isKotlinSuspendFunction = true; // 标记为挂起函数
return null; // 不需要为 Continuation 创建参数处理器
}
// ... 处理其他参数
}
关键原理:Kotlin 编译器在编译 suspend 函数时,会在字节码中自动添加一个隐藏的 Continuation 参数。Retrofit 通过检查方法参数列表中是否存在 Continuation.class 类型来判断该方法是否为挂起函数。
完整请求处理流程
步骤 1:代理调用
// HttpServiceMethod.class
@Override
final @Nullable ReturnT invoke(Object instance, Object[] args) {
// 创建实际的网络调用
Call<ResponseT> call = new OkHttpCall<>(requestFactory, instance, args, callFactory, responseConverter);
return adapt(call, args); // 关键:适配到协程
}
步骤 2:Suspend 函数适配
// SuspendForBody.class - 专门处理挂起函数的适配器
@Override
protected Object adapt(Call<ResponseT> call, Object[] args) {
call = callAdapter.adapt(call);
// 从参数列表获取 Continuation(总是最后一个参数)
Continuation<ResponseT> continuation = (Continuation<ResponseT>) args[args.length - 1];
try {
// 根据返回类型选择合适的 await 方法
if (isUnit) {
return KotlinExtensions.awaitUnit((Call<Unit>) call, (Continuation<Unit>) continuation);
} else if (isNullable) {
return KotlinExtensions.awaitNullable(call, continuation);
} else {
return KotlinExtensions.await(call, continuation); // 最常见的非空情况
}
} catch (Throwable e) {
return KotlinExtensions.suspendAndThrow(e, continuation);
}
}
步骤 3:协程转换核心(KotlinExtensions.kt)
// KotlinExtensions.kt
suspend fun <T : Any> Call<T>.await(): T {
return suspendCancellableCoroutine { continuation ->
// 1. 设置取消回调
continuation.invokeOnCancellation {
cancel() // 如果协程被取消,则取消网络请求
}
// 2. 发起异步网络请求
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful) {
val body = response.body()
if (body == null) {
// 处理空响应体
val e = KotlinNullPointerException("Response body was null")
continuation.resumeWithException(e)
} else {
// 成功:恢复协程并返回结果
continuation.resume(body)
}
} else {
// HTTP 错误码
continuation.resumeWithException(HttpException(response))
}
}
override fun onFailure(call: Call<T>, t: Throwable) {
// 网络请求失败
continuation.resumeWithException(t)
}
})
}
}
技术要点总结
| 机制 | Retrofit 实现方式 | 作用 |
|---|---|---|
| Suspend 检测 | 检查方法参数是否包含 Continuation.class |
识别 Kotlin 挂起函数 |
| 动态代理 | Proxy.newProxyInstance 创建接口实现 |
拦截方法调用 |
| 请求构建 | RequestFactory 解析注解和参数 |
创建 OkHttpCall |
| 协程适配 | SuspendForBody 等适配器类 |
连接 Retrofit 回调与协程 |
| 回调转换 | suspendCancellableCoroutine + Callback |
将异步回调转换为挂起函数 |
| 取消支持 | invokeOnCancellation 回调 |
实现协程取消时取消网络请求 |
为什么这样设计?
- 向后兼容:通过检查
Continuation参数而不是使用注解,Retrofit 可以同时支持普通函数和挂起函数,无需破坏性更改。 - 性能优化:只在运行时检测一次是否为挂起函数,后续调用直接使用缓存的
ServiceMethod。 - 错误处理:通过
resumeWithException将网络错误、HTTP 错误和空响应统一转换为异常,方便协程中处理。 - 取消传播:天然支持结构化并发,当协程作用域取消时,网络请求也会被正确取消。
这种设计让 Retrofit 在 Kotlin 协程生态中提供了零样板代码的网络调用体验,开发者可以像调用本地函数一样进行网络请求,同时享受协程的所有优势:结构化并发、取消传播、错误处理等。