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
}
关键步骤:
- 注解解析:解析
@GET、@POST、@Path、@Query等注解,构建请求模板 - ServiceMethod 缓存:解析结果缓存到
ConcurrentHashMap,避免反射开销 - CallAdapter 适配:将
Call<T>转换为suspend fun、Flow<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 | 优势 | 劣势 |
|---|---|---|
| Gson | API 简单、社区广泛 | 反射开销、不支持 Kotlin 默认值 |
| Moshi | Kotlin 友好、codegen 高效 | 注解略多 |
| kotlinx.serialization | Kotlin 原生、编译期生成 | 需 plugin 配置 |
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 T、Observable<T>、Flow<T>)。例如RxJava3CallAdapterFactory将 Call 适配为 Observable。
两者是独立工作的,Converter 处理数据层面,CallAdapter 处理线程/异步层面。
Q4: Retrofit 如何处理错误响应?
答案:
- HTTP 错误(4xx/5xx):如果返回类型是
Response<T>,不抛异常,可以通过response.code()和response.errorBody()获取错误信息;如果返回类型直接是T,则抛出HttpException - 网络错误:抛出
IOException(如SocketTimeoutException、UnknownHostException) - 数据解析错误:抛出
JsonSyntaxException等 Converter 层面的异常
最佳实践是用 sealed class 封装统一的 ApiResult,在 Repository 层统一捕获和转换。