跳到主要内容

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()

连接复用条件

  1. 相同的 host + port
  2. 相同的 TLS 版本和 CipherSuite
  3. 连接未关闭且未超时
连接池清理机制

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

OkHttpClient 持有连接池和线程池,应作为全局单例使用。如需不同配置,使用 client.newBuilder() 创建共享底层资源的新实例。


常见面试问题

Q1: OkHttp 的拦截器链如何工作?

答案

OkHttp 使用责任链模式。所有拦截器组成链,每个拦截器调用 chain.proceed(request) 将请求传递给下一个拦截器,并接收其返回的 Response。执行顺序是:自定义拦截器 → RetryAndFollowUp → Bridge → Cache → Connect → Network 拦截器 → CallServer。每个拦截器可以在 proceed 前修改 Request,在 proceed 后修改 Response。

Q2: addInterceptor 和 addNetworkInterceptor 的区别?

答案

特性addInterceptoraddNetworkInterceptor
位置所有拦截器最前面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() 共享底层资源。

相关链接