本文将深入 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 回调 实现协程取消时取消网络请求

为什么这样设计?

  1. 向后兼容:通过检查 Continuation 参数而不是使用注解,Retrofit 可以同时支持普通函数和挂起函数,无需破坏性更改。
  2. 性能优化:只在运行时检测一次是否为挂起函数,后续调用直接使用缓存的 ServiceMethod
  3. 错误处理:通过 resumeWithException 将网络错误、HTTP 错误和空响应统一转换为异常,方便协程中处理。
  4. 取消传播:天然支持结构化并发,当协程作用域取消时,网络请求也会被正确取消。

这种设计让 Retrofit 在 Kotlin 协程生态中提供了零样板代码的网络调用体验,开发者可以像调用本地函数一样进行网络请求,同时享受协程的所有优势:结构化并发、取消传播、错误处理等。