跳到主要内容

Retrofit 设计分析

问题

Retrofit 的核心设计思想和工作原理是什么?

答案

Retrofit 架构概览

Retrofit 是一个类型安全的 HTTP 客户端,通过注解 + 动态代理将 HTTP API 转换为 Kotlin/Java 接口调用:

1. 基本使用

// 1. 定义 API 接口
interface ApiService {
@GET("users/{id}")
suspend fun getUser(@Path("id") userId: String): User

@POST("users")
suspend fun createUser(@Body user: CreateUserRequest): User

@GET("users")
suspend fun getUsers(
@Query("page") page: Int,
@Query("size") size: Int
): List<User>

@Multipart
@POST("upload")
suspend fun uploadFile(
@Part file: MultipartBody.Part,
@Part("description") description: RequestBody
): UploadResponse
}

// 2. 创建 Retrofit 实例
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(okHttpClient) // OkHttp 实例
.addConverterFactory(GsonConverterFactory.create()) // JSON 转换器
.build()

// 3. 创建 API 实例并调用
val api = retrofit.create(ApiService::class.java)
val user = api.getUser("123") // 协程挂起调用

2. 动态代理核心原理

Retrofit.create() 使用 JDK 动态代理 将接口方法转换为 HTTP 请求:

// Retrofit.create() 简化伪代码
fun <T> create(service: Class<T>): T {
return Proxy.newProxyInstance(
service.classLoader,
arrayOf(service)
) { proxy, method, args ->
// 1. 解析方法注解,生成 ServiceMethod
val serviceMethod = loadServiceMethod(method)
// 2. 创建 OkHttpCall
val call = OkHttpCall(serviceMethod, args)
// 3. 通过 CallAdapter 适配返回类型
serviceMethod.callAdapter.adapt(call)
} as T
}

关键步骤

  1. 注解解析:解析 @GET@POST@Path@Query 等注解,构建请求模板
  2. ServiceMethod 缓存:解析结果缓存到 ConcurrentHashMap,避免反射开销
  3. CallAdapter 适配:将 Call<T> 转换为 suspend funFlow<T>

3. Converter 转换器

Converter 负责请求体序列化和响应体反序列化:

// 常用 Converter
Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create()) // Gson
.addConverterFactory(MoshiConverterFactory.create()) // Moshi(推荐)
.addConverterFactory(KotlinxSerializationConverterFactory // kotlinx.serialization
.create(Json { ignoreUnknownKeys = true }))
.build()
Converter优势劣势
GsonAPI 简单、社区广泛反射开销、不支持 Kotlin 默认值
MoshiKotlin 友好、codegen 高效注解略多
kotlinx.serializationKotlin 原生、编译期生成需 plugin 配置
推荐 Moshi 或 kotlinx.serialization

Gson 无法正确处理 Kotlin 非空类型和默认参数值,容易导致空安全问题。

4. CallAdapter 适配器

CallAdapter 控制 Retrofit 接口方法的返回类型:

// 不同的返回类型
interface ApiService {
// 默认:协程挂起
@GET("users/{id}")
suspend fun getUser(@Path("id") id: String): User

// 返回 Response 包装(可获取 HTTP 状态码、Headers)
@GET("users/{id}")
suspend fun getUserResponse(@Path("id") id: String): Response<User>

// 返回 Call(传统回调方式)
@GET("users/{id}")
fun getUserCall(@Path("id") id: String): Call<User>

// RxJava(需要 adapter-rxjava3)
@GET("users/{id}")
fun getUserRx(@Path("id") id: String): Single<User>
}

5. 协程支持原理

Retrofit 2.6+ 原生支持 suspend 函数:

// Retrofit 检测到 suspend 函数时的处理流程:
// 1. 识别最后一个参数为 Continuation(Kotlin 编译器自动添加)
// 2. 使用 KotlinExtensions.await() 将 Call 转换为挂起调用
// 3. 在 OkHttp Dispatcher 的线程池中执行网络请求
// 4. 通过 Continuation 恢复协程

// suspend 函数异常处理
try {
val user = api.getUser("123")
} catch (e: HttpException) {
// HTTP 错误(4xx、5xx)
val errorBody = e.response()?.errorBody()?.string()
} catch (e: IOException) {
// 网络错误(无网络、超时等)
}

6. 封装 Retrofit 网络层

// 统一 Result 包装
sealed class ApiResult<out T> {
data class Success<T>(val data: T) : ApiResult<T>()
data class Error(val code: Int, val message: String) : ApiResult<Nothing>()
data class Exception(val e: Throwable) : ApiResult<Nothing>()
}

// 安全调用扩展函数
suspend fun <T> safeApiCall(
apiCall: suspend () -> T
): ApiResult<T> {
return try {
ApiResult.Success(apiCall())
} catch (e: HttpException) {
ApiResult.Error(e.code(), e.message())
} catch (e: IOException) {
ApiResult.Exception(e)
}
}

// 使用
val result = safeApiCall { api.getUser("123") }
when (result) {
is ApiResult.Success -> showUser(result.data)
is ApiResult.Error -> showError(result.message)
is ApiResult.Exception -> showNetworkError()
}

常见面试问题

Q1: Retrofit 的动态代理是如何工作的?

答案

Retrofit.create() 调用 Proxy.newProxyInstance() 创建接口的代理实现。当调用接口方法时,InvocationHandler 拦截方法调用,解析方法上的注解(@GET@POST 等)和参数上的注解(@Path@Query 等),构建 ServiceMethod 对象,然后将其转化为 OkHttp 的 Request。解析结果会被缓存到 ConcurrentHashMap 中,同一方法只解析一次。

Q2: Retrofit 如何支持 Kotlin 协程?

答案

Kotlin 编译器会将 suspend 函数的最后一个参数编译为 Continuation 类型。Retrofit 通过检查方法参数判断是否为 suspend 函数。如果是,Retrofit 内部使用 suspendCancellableCoroutine 将 OkHttp 的 Call.enqueue() 异步回调转换为协程挂起/恢复,当响应到达时通过 continuation.resume() 恢复协程。

Q3: Converter 和 CallAdapter 的区别?

答案

  • Converter:负责数据格式转换 — 将 RequestBody ↔ Java/Kotlin 对象。例如 GsonConverterFactory 将 JSON 字符串转为对象。
  • CallAdapter:负责调用方式适配 — 将 Call<T> 转换为其他类型(suspend TObservable<T>Flow<T>)。例如 RxJava3CallAdapterFactory 将 Call 适配为 Observable。

两者是独立工作的,Converter 处理数据层面,CallAdapter 处理线程/异步层面。

Q4: Retrofit 如何处理错误响应?

答案

  • HTTP 错误(4xx/5xx):如果返回类型是 Response<T>,不抛异常,可以通过 response.code()response.errorBody() 获取错误信息;如果返回类型直接是 T,则抛出 HttpException
  • 网络错误:抛出 IOException(如 SocketTimeoutExceptionUnknownHostException
  • 数据解析错误:抛出 JsonSyntaxException 等 Converter 层面的异常

最佳实践是用 sealed class 封装统一的 ApiResult,在 Repository 层统一捕获和转换。

相关链接