Retrofit + OkHttp 架构解析
🌐 Retrofit + OkHttp 架构解析
面试高频指数:⭐⭐⭐⭐⭐ 网络请求是 App 的核心,Retrofit + OkHttp 是 Android 事实标准的网络栈。
1. 整体架构
调用方(协程/回调)
│ 调用接口方法
▼
Retrofit(动态代理)
│ 解析注解 → 生成 Request
▼
OkHttp Call
│ 拦截器链(责任链)
▼
连接池 → Socket → 服务器
│
▼
Response → 转换器(Gson/Moshi)→ 业务对象2. Retrofit 原理:动态代理
2.1 为什么接口不需要实现类
interface ApiService {
@GET("users/{id}")
suspend fun getUser(@Path("id") id: Int): User
}
// 使用
val api = retrofit.create(ApiService::class.java) // 没有实现类!2.2 动态代理机制
// Retrofit.create 源码核心(简化)
public <T> T create(final Class<T> service) {
return (T) Proxy.newProxyInstance( // JDK 动态代理
service.getClassLoader(),
new Class<?>[]{service},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
// 每个接口方法调用都会走到这里
ServiceMethod serviceMethod = loadServiceMethod(method); // 缓存解析结果
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.adapt(okHttpCall); // 适配为 Call/suspend/Observable
}
});
}关键点:
Proxy.newProxyInstance:运行时生成接口的代理对象。- 方法缓存:
loadServiceMethod缓存已解析的方法(ConcurrentHashMap),避免重复解析。 - 适配器:
CallAdapter把OkHttpCall适配成协程suspend/Observable/Call。
2.3 ServiceMethod 解析过程
方法注解(@GET/@POST)→ 请求方式
方法参数注解(@Path/@Query/@Body)→ 参数位置
返回值类型 → CallAdapter 选择
参数转换器(Converter)→ 序列化/反序列化3. OkHttp 请求链路
3.1 拦截器链(责任链模式)
用户 Call
├─ ① Application Interceptors(自定义,最先执行)
├─ ② RetryAndFollowUpInterceptor(重试/重定向)
├─ ③ BridgeInterceptor(补全请求头)
├─ ④ CacheInterceptor(缓存处理)
├─ ⑤ ConnectInterceptor(建立连接)
└─ ⑥ CallServerInterceptor(读写数据)// 添加应用拦截器(全局,可看到所有请求)
val client = OkHttpClient.Builder()
.addInterceptor { chain -> // 应用拦截器
val request = chain.request()
.newBuilder()
.header("Authorization", "Bearer $token")
.build()
val response = chain.proceed(request)
// 统一处理错误码
response
}
.addNetworkInterceptor { chain -> // 网络拦截器(仅真实网络请求)
val t1 = System.nanoTime()
val response = chain.proceed(chain.request())
Log.d("OkHttp", "耗时: ${(System.nanoTime() - t1) / 1e6}ms")
response
}
.build()应用拦截器 vs 网络拦截器:
| 区别 | 应用拦截器 | 网络拦截器 |
|---|---|---|
| 调用次数 | 请求一次 | 重定向/重试会多次 |
| 能看到 | 原始请求 | 实际网络请求(含重定向后) |
| 位置 | 链最前 | 链中间(连接之后) |
| 典型用途 | 加 Token、日志 | 统计真实网络耗时 |
3.2 连接池
// OkHttp 默认:每个 Host 最多 5 个空闲连接,存活 5 分钟
ConnectionPool(maxIdleConnections = 5, keepAliveDuration = 5, TimeUnit.MINUTES)- HTTP/1.1:
keep-alive复用连接,避免重复 TCP 握手。 - HTTP/2:多路复用,一个连接并发多个请求。
- 连接池按
Route(地址+代理+TLS)分组。
3.3 请求执行流程
RealCall.execute()(同步)
└─ getResponseWithInterceptorChain() // 构建拦截器链
└─ chain.proceed(request) // 依次经过每个拦截器
└─ CallServerInterceptor // 最终写请求、读响应4. 协程支持
// Retrofit 2.6+ 原生支持 suspend
interface ApiService {
@GET("users/{id}")
suspend fun getUser(@Path("id") id: Int): User
}
// 内部原理:suspend 函数通过 suspendCancellableCoroutine 包装
// 请求完成回调 resume 协程,取消时 cancel Call// 使用(无需手动切线程)
viewModelScope.launch {
try {
val user = api.getUser(42) // 自动在 IO 线程执行
uiState.value = user
} catch (e: HttpException) {
// HTTP 错误码
} catch (e: IOException) {
// 网络错误
}
}5. 高频面试题
Q1:Retrofit 动态代理原理? A:create() 使用 JDK Proxy.newProxyInstance 为接口生成代理对象,方法调用统一 进入 InvocationHandler.invoke,通过 loadServiceMethod(缓存)解析注解与参数, 构造 OkHttpCall,再由 CallAdapter 适配为 suspend/Observable/Call。
Q2:为什么 Retrofit 接口方法第一次调用慢? A:首次调用需要解析注解生成 ServiceMethod(反射开销),之后走缓存 (ConcurrentHashMap)。可启动时预热接口。
Q3:应用拦截器和网络拦截器的区别? A:应用拦截器在链头、每个请求只调一次、看到的是原始请求;网络拦截器在连接之后、 重定向/重试会多次调用、看到真实网络请求。日志、鉴权用应用拦截器;真实耗时统计用网络拦截器。
Q4:OkHttp 如何复用连接? A:ConnectionPool 按 Route 缓存空闲连接;HTTP/1.1 用 keep-alive,HTTP/2 多路复用。 CallServerInterceptor 使用完连接归还池(Exchange 复用机制)。
Q5:Retrofit suspend 函数内部如何处理协程取消? A:suspend 扩展通过 suspendCancellableCoroutine 实现,Call.enqueue 回调中 resume;协程取消时调用 call.cancel() 取消网络请求,避免资源浪费。
6. 小结
- Retrofit:动态代理 + 注解解析 + CallAdapter/Converter 可插拔。
- OkHttp:拦截器责任链 + 连接池 + HTTP/2 多路复用。
- 协程时代:
suspend函数直接返回结果,异常用 try-catch。 - 面试重点:动态代理原理、拦截器链、连接复用、协程适配。