一、定位
Retrofit 是一个类型安全的 HTTP 客户端,本质是对 OkHttp 的"声明式上层封装 ":你用接口 + 注解描述请求,它在运行时用动态代理 生成接口实现,把方法调用翻译成 OkHttp 请求,再通过 CallAdapter 决定返回类型、Converter 负责序列化/反序列化。
Retrofit = 动态代理(Proxy) + 注解解析(RequestFactory) + 调用适配(CallAdapter) + 数据转换(Converter) + 底层执行(OkHttp Call)
二、基本用法
kotlin
// 1. 定义接口
interface ApiService {
@GET("users/{id}")
suspend fun getUser(@Path("id") id: Int): User
@POST("users")
suspend fun createUser(@Body user: User): Response<User>
}
// 2. 构建 Retrofit
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/") // 必须以 / 结尾
.client(okHttpClient) // 底层 OkHttp
.addConverterFactory(GsonConverterFactory.create())
.build()
// 3. 创建代理实例并调用
val api = retrofit.create(ApiService::class.java)
val user = api.getUser(1) // 一行代码背后是下面整条链路
三、核心原理逐层拆解
3.1 动态代理:create() 发生了什么
java
// Retrofit#create() 源码核心(简化)
public <T> T create(final Class<T> service) {
validateServiceInterface(service); // 校验必须是接口、不能继承其他接口
return (T) Proxy.newProxyInstance(
service.getClassLoader(),
new Class<?>[] { service },
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args); // toString/equals 等直接执行
}
// 核心:加载(或从缓存取)该方法的 ServiceMethod,执行
return loadServiceMethod(method).invoke(args);
}
});
}
关键点:
- 为什么用动态代理:接口没有实现类,JDK 动态代理在运行时生成实现,所有方法调用统一收编到 InvocationHandler.invoke()------这里就是把"方法调用"转成"HTTP 请求"的总入口。
- ServiceMethod 缓存 :loadServiceMethod() 内部是一个 ConcurrentHashMap<Method, ServiceMethod>。反射解析注解的性能开销只发生在每个方法第一次调用时,之后命中缓存。所以 Retrofit 的反射不是性能问题。
- create() 本身很便宜,可以全局单例 Retrofit、按需 create 多个 Service。
3.2 注解解析:ServiceMethod 与 RequestFactory
每个接口方法最终被解析成一个 ServiceMethod,它封装了一次请求的全部信息:
ServiceMethod.parseAnnotations(retrofit, method)
│
├── RequestFactory.parseAnnotations() → 解析出"请求长什么样"
│ ├── 方法注解:@GET/@POST/@HTTP → httpMethod + relativeUrl
│ └── 参数注解:逐个解析成 ParameterHandler
│ (@Path→替换URL占位符 / @Query→拼查询串 / @Body→RequestBody / @Header...)
│
├── createCallAdapter() → 决定"返回类型怎么适配"
└── createResponseConverter() → 决定"响应体怎么反序列化"
RequestFactory.Builder 解析出的关键要素 :HTTP 方法、相对路径、Headers、ParameterHandler\[\] 数组(每个参数对应一个处理器,负责把自己写进 RequestBuilder)。这一步的产物是一份与执行无关的请求模板,线程安全、可复用。
3.3 返回类型分发:HttpServiceMethod 的三态
HttpServiceMethod.parseAnnotations() 会根据方法签名走三条分支:
| 方法签名 | 分支类 | 行为 |
|---|---|---|
fun get(): Call<User> |
CallAdapted | 交给 CallAdapter 适配(默认返回 Call,或 RxJava 的 Observable 等) |
suspend fun get(): Response<User> |
SuspendForResponse | 挂起执行,恢复时给完整 Response |
suspend fun get(): User |
SuspendForBody | 挂起执行,恢复时只给 body,非 2xx 抛 HttpException |
判断 suspend 的方式:method.getParameterTypes() 最后一个参数是 Continuation 类型(Kotlin suspend 函数编译后会多一个续体参数)------这是 Retrofit 支持协程的入口识别点。
3.4 底层执行:OkHttpCall
retrofit2.Call 的默认实现是 OkHttpCall,它是 okhttp3.Call 的装饰器:
OkHttpCall.enqueue(callback)
└── callFactory.newCall(requestFactory.create(args)) // 用模板 + 实参构建真实 OkHttp 请求
└── okhttp3.Call.enqueue(okhttp Callback)
└── onResponse → parseResponse(rawResponse)
├── code 2xx → Response.success(converter.convert(body))
└── 非 2xx → 缓冲 errorBody → Response.error(...)
线程模型:
- execute():同步,在调用线程执行,Android 主线程调用会崩(NetworkOnMainThreadException)
- enqueue():异步,网络请求跑在 OkHttp Dispatcher 的线程池 ;回调默认经过 callbackExecutor 切回主线程 ------Android 上 Retrofit 通过 Platform 检测到 Android 环境,注入 MainThreadExecutor(内部 Handler.post)。所以 Retrofit 的 onResponse 默认在主线程,这就是为什么老代码可以直接在回调里更新 UI
3.5 suspend 支持的本质
suspend fun getUser(): User 最终走到 KotlinExtensions.await(),核心代码:
kotlin
suspend fun <T> Call<T>.await(): T {
return suspendCancellableCoroutine { continuation ->
// 协程取消 → 取消 OkHttp 请求
continuation.invokeOnCancellation { cancel() }
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful) {
continuation.resume(response.body()!!) // 成功:恢复协程
} else {
continuation.resumeWithException(HttpException(response))
}
}
override fun onFailure(call: Call<T>, t: Throwable) {
continuation.resumeWithException(t) // 失败:带异常恢复
}
})
}
}
一句话答案:Retrofit 对 suspend 的支持 = suspendCancellableCoroutine 把回调式 enqueue 桥接成挂起函数 ,请求仍在 OkHttp 的 IO 线程池执行,协程挂起不阻塞线程,取消协程会联动 call.cancel() 断掉 HTTP 连接。
3.6 两大扩展点(策略模式)
CallAdapter.Factory ------ 决定方法返回类型:
工厂按添加顺序遍历,get(returnType, ...) 返回第一个非 null 的适配器
├── DefaultCallAdapterFactory(内置兜底):支持 Call<T>,包一层 ExecutorCallbackCall 切主线程
├── RxJava2CallAdapterFactory:Observable/Single/Completable
└── 自定义:比如返回 LiveData<T>、Result<T>
Converter.Factory ------ 负责数据转换,三个层级:
| 方法 | 用途 |
|---|---|
responseBodyConverter |
ResponseBody → Java/Kotlin 对象(Gson/Moshi/kotlinx.serialization) |
requestBodyConverter |
对象 → RequestBody(@Body 参数序列化) |
stringConverter |
对象 → String(@Path/@Query/@Header 参数转字符串,内置 EnumConverter 等) |
同样是工厂按注册顺序遍历,先到先得------所以内置的 BuiltInConverters 在最后,你要自定义解析(比如加密响应)就把自己的 Factory 加在 Gson 前面。
四、注解速查表
| 分类 | 注解 | 说明 |
|---|---|---|
| HTTP 方法 | @GET @POST @PUT @DELETE @PATCH @HEAD @OPTIONS | 括号内是相对路径 |
| 自定义方法 | @HTTP(method="...", path="...", hasBody=...) | 少见方法用 |
| 标记 | @FormUrlEncoded | 表单提交,配合 @Field/@FieldMap |
| 标记 | @Multipart | 文件/多部分上传,配合 @Part/@PartMap |
| 标记 | @Streaming | 大文件下载,不一次性读入内存,流式写盘 |
| 参数 | @Path("id") | 替换 URL 中 {id} 占位符 |
| 参数 | @Query("page") / @QueryMap | 拼接查询参数 |
| 参数 | @Url | 动态完整 URL,会覆盖 baseUrl 拼接 |
| 参数 | @Body | 对象序列化为请求体 |
| 参数 | @Header("Authorization") / @Headers | 动态/静态请求头 |
| 参数 | @Tag | 给请求打标,拦截器里 request.tag() 取出做差异化处理 |
baseUrl 拼接规则:baseUrl 必须以 / 结尾;接口路径以 / 开头表示域名根路径绝对定位,不以 / 开头则相对 baseUrl 拼接。
五、实战标准配置
kotlin
val okHttpClient = OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
// 日志拦截器(release 包记得关掉或降级为 BASIC/NONE)
.addInterceptor(HttpLoggingInterceptor().apply {
level = if (BuildConfig.DEBUG) BODY else NONE
})
// Token 注入拦截器(应用拦截器,能看到最终请求)
.addInterceptor { chain ->
val request = chain.request().newBuilder()
.addHeader("Authorization", "Bearer ${TokenManager.get()}")
.build()
chain.proceed(request)
}
// Token 过期自动刷新重试(Authenticator,只在 401 时触发)
.authenticator { route, response ->
val newToken = runBlocking { TokenManager.refresh() }
response.request.newBuilder()
.header("Authorization", "Bearer $newToken")
.build()
}
.build()
统一错误封装(现代写法):
kotlin
sealed interface 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 throwable: Throwable) : ApiResult<Nothing>
}
suspend fun <T> apiCall(block: suspend () -> T): ApiResult<T> = try {
ApiResult.Success(block())
} catch (e: HttpException) { // 非 2xx
ApiResult.Error(e.code(), e.message())
} catch (e: IOException) { // 网络异常
ApiResult.Exception(e)
}
六、工作流程
以这行代码为起点,拆解它背后发生的全部事情:
kotlin
val user = api.getUser(1) // suspend fun getUser(@Path("id") id: Int): User
阶段 0:初始化(App 启动时,只做一次)
Retrofit.Builder()
.baseUrl(...) → 记录基础 URL(必须 / 结尾)
.client(okHttpClient) → 记录 callFactory(真实执行者)
.addConverterFactory(...) → 装入 Converter 工厂列表
.addCallAdapterFactory(...) → 装入 CallAdapter 工厂列表
.build() → 检测平台(Android → 注入 MainThreadExecutor)
→ 生成 Retrofit 实例(全局单例)
此阶段只存配置,不解析任何接口、不做任何网络操作。
阶段 1:创建代理(retrofit.create())
retrofit.create(ApiService::class.java)
│
├─ 校验:必须是接口、不能继承其他接口、不能有类型参数
│
└─ Proxy.newProxyInstance(classLoader, [ApiService], invocationHandler)
→ 运行时动态生成 ApiService 的实现类(代理对象)
→ 该接口的所有方法调用,都会被收编到
InvocationHandler.invoke(proxy, method, args)
此阶段依然没有任何注解解析和网络操作,代理创建非常便宜。
阶段 2:首次调用------方法解析(每个方法只做一次)
api.getUser(1)
│
└─ InvocationHandler.invoke(method=getUser, args=[1])
│
├─ method 属于 Object?(toString 等)→ 直接执行返回
│
└─ loadServiceMethod(method)
│
├─ 查缓存 ConcurrentHashMap<Method, ServiceMethod>
│ ├─ 命中 → 直接返回(以后每次调用都走这里,零反射)
│ └─ 未命中 → 解析(仅此一次)↓
│
└─ ServiceMethod.parseAnnotations(retrofit, method)
│
├─ ① RequestFactory.parseAnnotations()
│ 解析方法注解:@GET → httpMethod="GET", relativeUrl="users/{id}"
│ 解析参数注解:@Path("id") → ParameterHandler.Path
│ 产物:与实参无关的"请求模板"(线程安全、可复用)
│
├─ ② 识别方法签名 → 确定执行分支
│ 最后一个参数是 Continuation?→ suspend 分支
│ (SuspendForResponse / SuspendForBody)
│ 否则 → CallAdapted 分支
│
├─ ③ createCallAdapter()
│ 遍历 CallAdapter.Factory 列表,第一个匹配的胜出
│
└─ ④ createResponseConverter()
遍历 Converter.Factory 列表,找到 User 类型的反序列化器
反射开销全部集中在这里,且每个方法只发生一次------这是"Retrofit 用反射为什么不怕性能问题"的标准答案。
阶段 3:构建真实请求(每次调用都发生)
ServiceMethod.invoke(args=[1])
│
└─ RequestFactory.create(args)
│
├─ new RequestBuilder(以解析好的模板为底)
├─ 遍历 ParameterHandler[] 数组,把实参"写"进请求:
│ @Path → "users/{id}" 中的 {id} 替换为 "1" → "users/1"
│ @Query → 拼查询串 @Body → Converter 序列化为 RequestBody
│ @Header→ 加请求头
├─ baseUrl + relativeUrl 拼接出完整 URL
└─ 生成 okhttp3.Request
阶段 4:交给 OkHttp 执行
OkHttpCall(装饰 okhttp3.Call)
│
├─ suspend/异步路线:call.enqueue(okhttp Callback)
│ → OkHttp Dispatcher 线程池调度
│ → 拦截器链:应用拦截器 → RetryAndFollowUp → Bridge
│ → Cache → Connect(连接池复用/TLS)→ CallServer
│ → 真正发出 HTTP 请求,等待响应
│
└─ 同步路线:call.execute()(调用线程直接执行,主线程禁用)
Retrofit 自己到此为止------网络 IO 全是 OkHttp 的事。
阶段 5:响应处理与交付(分两条支线)
onResponse(rawResponse)
│
└─ parseResponse(rawResponse)
├─ code 2xx → Response.success(converter.convert(body))
│ (Gson/Moshi:ResponseBody → User 对象)
└─ 非 2xx → 缓冲 errorBody → Response.error(...)
支线 A:Call
ExecutorCallbackCall(装饰器)
→ callbackExecutor.execute { callback.onResponse(...) }
→ Handler.post 切回主线程 → 你在回调里直接更新 UI
支线 B:suspend 路线(现代主流)
suspendCancellableCoroutine
├─ 成功 → continuation.resume(user) 协程在调用处恢复,拿到 User
├─ 失败 → resumeWithException(...) 走 catch
└─ 协程被取消 → call.cancel() 联动断开 HTTP 连接
七、 整体流程概览
你的代码 api.getUser(1)
│
▼
动态代理 InvocationHandler.invoke ← create() 时生成
│
▼
loadServiceMethod(ConcurrentHashMap 缓存) ← 首次反射解析注解,之后零反射
│
▼
RequestFactory + 实参 → okhttp3.Request ← ParameterHandler 逐个写入
│
▼
OkHttpCall → OkHttp 拦截器链 → 服务器 ← 真正的网络 IO(IO 线程池)
│
▼
parseResponse → Converter.convert ← JSON → 对象
│
▼
交付:Call 回调切主线程 / suspend 恢复协程 ← CallAdapter 决定的形态
Retrofit 用动态代理把接口方法调用收编到 invoke();首次调用时反射解析注解生成 ServiceMethod 并缓存,其中 RequestFactory 负责拼请求、CallAdapter 决定返回类型、Converter 负责数据转换;之后每次调用用模板+实参构建 OkHttp Request,交给 OkHttp 执行;响应回来后 Converter 反序列化,回调路线经 Executor 切回主线程,suspend 路线通过 suspendCancellableCoroutine 恢复协程。Retrofit 全程不做网络 IO,它是 OkHttp 的声明式封装层。
八、设计模式总结
| 模式 | 体现 |
|---|---|
| 动态代理 | create() 生成接口实现,统一收编方法调用 |
| 建造者模式 | Retrofit.Builder、Request.Builder(链式配置复杂对象) |
| 适配器模式 | CallAdapter(把 OkHttp Call 适配成 Call/RxJava/suspend 各种返回类型) |
| 策略模式 | Converter(序列化策略可插拔:Gson/Moshi/Protobuf) |
| 工厂模式 | CallAdapter.Factory、Converter.Factory(按类型遍历匹配) |
| 装饰器模式 | OkHttpCall 装饰 okhttp3.Call;ExecutorCallbackCall 装饰回调切线程 |
| 外观模式 | Retrofit 本身是 OkHttp 复杂能力的简化门面 |
九、常见问题
- Retrofit 的原理?
------ 用第一节那个公式回答,然后逐层展开动态代理 → 注解解析 → CallAdapter/Converter → OkHttp 执行。 - 反射性能差,Retrofit 为什么敢用?
------ 注解解析只在方法首次调用时发生,ServiceMethod 存 ConcurrentHashMap 缓存;代理创建本身只生成字节码,热点在 OkHttp。 - suspend 函数是怎么支持的?
------ 编译后方法多一个 Continuation 参数被识别 → SuspendForBody/SuspendForResponse 分支 → suspendCancellableCoroutine 桥接 enqueue 回调,取消联动 call.cancel()。 - 回调在哪个线程?
------ enqueue 回调经 callbackExecutor 切主线程(Android 平台注入MainThreadExecutor);suspend 版本由协程调度器决定,恢复在调用方上下文。 - CallAdapter 和 Converter 的区别?
------ CallAdapter 管"返回类型"(Call/Observable/suspend body),Converter 管"数据怎么转"(JSON↔对象)。匹配都是工厂顺序遍历、先到先得。 - 如何上传文件 / 下载大文件?
------ @Multipart + @Part(MultipartBody.Part);@Streaming + ResponseBody.byteStream() 分块写盘(注意别开 Gson converter 转它)。 - 如何做 Token 过期自动刷新?
------ OkHttp Authenticator(401 触发,同步刷新并重放请求),与应用拦截器的 Token 注入配合。 - Retrofit 与 OkHttp 分工?
------ Retrofit 管"接口抽象、注解解析、类型适配、数据转换";OkHttp 管"连接池、拦截器链、缓存、HTTP/2、实际收发"。Retrofit 自己不做任何网络 IO。