OkHttp 核心原理
问题
OkHttp 的核心架构和工作原理是什么?
答案
OkHttp 整体架构
1. Dispatcher 调度器
Dispatcher 管理异步请求的执行策略:
// OkHttp Dispatcher 核心参数
val client = OkHttpClient.Builder()
.dispatcher(Dispatcher().apply {
maxRequests = 64 // 最大同时请求数
maxRequestsPerHost = 5 // 同一 Host 最大并发
})
.build()
调度流程:
- 同步请求(
execute()):直接在当前线程执行 - 异步请求(
enqueue()):通过ExecutorService线程池调度
2. 拦截器链(责任链模式)
OkHttp 最核心的设计 —— 所有网络操作都通过拦截器链完成:
// 拦截器链调用核心逻辑
internal class RealInterceptorChain(
private val interceptors: List<Interceptor>,
private val index: Int,
// ... 其他参数
) : Interceptor.Chain {
override fun proceed(request: Request): Response {
// 创建下一个拦截器的 Chain
val next = copy(index = index + 1)
val interceptor = interceptors[index]
// 调用当前拦截器,传入 next chain
val response = interceptor.intercept(next)
return response
}
}
五大内置拦截器:
| 拦截器 | 职责 |
|---|---|
| RetryAndFollowUpInterceptor | 失败重试、301/302 重定向、最多 20 次 |
| BridgeInterceptor | 补充 Headers(Content-Type、Host、Accept-Encoding)、处理 Cookie、Gzip 解压 |
| CacheInterceptor | 读写缓存、判断缓存是否可用 |
| ConnectInterceptor | 查找/创建连接(TCP + TLS 握手) |
| CallServerInterceptor | 向服务器发送请求、读取响应 |
3. ConnectionPool 连接池
连接池复用 TCP 连接,避免频繁握手:
val client = OkHttpClient.Builder()
.connectionPool(ConnectionPool(
maxIdleConnections = 5, // 最大空闲连接数
keepAliveDuration = 5, // 保活时间
timeUnit = TimeUnit.MINUTES
))
.build()
连接复用条件:
- 相同的
host+port - 相同的 TLS 版本和 CipherSuite
- 连接未关闭且未超时
OkHttp 通过后台线程定时执行 cleanup(),清理超过 keepAliveDuration 的空闲连接和超过 maxIdleConnections 的多余连接。
4. 请求执行全流程
5. OkHttp 配置最佳实践
// 全局单例 OkHttpClient(推荐)
object HttpClient {
val instance: OkHttpClient by lazy {
OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
// 连接池
.connectionPool(ConnectionPool(5, 5, TimeUnit.MINUTES))
// 缓存(10MB)
.cache(Cache(
directory = File(context.cacheDir, "http_cache"),
maxSize = 10L * 1024 * 1024
))
// 自定义拦截器
.addInterceptor(LoggingInterceptor())
// 证书固定
.certificatePinner(CertificatePinner.Builder()
.add("api.example.com", "sha256/AAAA...")
.build())
.build()
}
}
OkHttpClient 持有连接池和线程池,应作为全局单例使用。如需不同配置,使用 client.newBuilder() 创建共享底层资源的新实例。
常见面试问题
Q1: OkHttp 的拦截器链如何工作?
答案:
OkHttp 使用责任链模式。所有拦截器组成链,每个拦截器调用 chain.proceed(request) 将请求传递给下一个拦截器,并接收其返回的 Response。执行顺序是:自定义拦截器 → RetryAndFollowUp → Bridge → Cache → Connect → Network 拦截器 → CallServer。每个拦截器可以在 proceed 前修改 Request,在 proceed 后修改 Response。
Q2: addInterceptor 和 addNetworkInterceptor 的区别?
答案:
| 特性 | addInterceptor | addNetworkInterceptor |
|---|---|---|
| 位置 | 所有拦截器最前面 | Connect 和 CallServer 之间 |
| 缓存命中时 | ✅ 会执行 | ❌ 不执行 |
| 重定向时 | 只执行 1 次 | 每次请求都执行 |
| 看到的 Request | 原始请求 | 经 Bridge 补充后的完整请求 |
| 典型用途 | 日志、统一参数 | 修改网络层 Header |
Q3: OkHttp 连接池复用原理是什么?
答案:
OkHttp 维护一个 ConnectionPool,缓存已建立的 TCP/TLS 连接。当新请求的 host:port 与池中某个空闲连接匹配时,直接复用,省去 TCP 三次握手和 TLS 握手的开销。默认最多保持 5 个空闲连接,每个连接空闲 5 分钟后自动回收。HTTP/2 场景下,同一 Host 的多个请求可复用一条连接(多路复用)。
Q4: OkHttp 如何实现请求重试?
答案:
RetryAndFollowUpInterceptor 负责重试逻辑:
- 路由异常:尝试下一个 IP 地址(DNS 返回多个 IP 时)
- 连接异常:如果是可恢复的 IOException,使用新连接重试
- 重定向:301/302/307/308 自动跟随,最多 20 次
- 认证:407 代理认证 / 401 服务端认证,触发
Authenticator回调
不会重试的情况:请求体已被 OneShot 消耗、协议错误 ProtocolException、证书异常。
Q5: 为什么 OkHttpClient 应该是单例?
答案:
OkHttpClient 内部持有:
- ConnectionPool:连接池中的 TCP 连接是昂贵资源
- Dispatcher:包含线程池
ExecutorService - Cache:磁盘缓存目录只能有一个实例控制
多实例会导致连接无法复用、线程资源浪费、缓存冲突。如需差异化配置(如不同的超时时间),使用 client.newBuilder() 共享底层资源。