粗粒度说一下OkHttp是如何设计的

63 阅读4分钟

1)架构分层(你要记住的几个核心角色)

  • API 层:OkHttpClient(不可变、Builder 配置)、Request/Response(不可变)、Call(一次请求任务,可 execute() 同步或 enqueue() 异步)。推荐复用一个 Client,newBuilder() 派生会共享连接池与线程池,减少握手与内存开销。

  • 拦截器链(Interceptors) :按顺序串起来的责任链,两类——应用拦截器(一次调用只走一次、可短路/重试)与 网络拦截器(每次真正的网络请求都会走一遍,能拿到底层 Connection)。拦截器负责监控、改写与重试。

  • 并发与调度:Dispatcher 管理异步并发上限(默认 总 64、同 host 5),内部用线程池执行;同步调用在调用方线程。

  • 连接系统:ConnectionPool 负责连接复用与空闲回收;Address/Route 把“URL + Client 配置”拆成静态地址动态路由;HTTP/2 在一条连接上多路复用,HTTP/1.1 每次一个流。

  • 缓存与 Cookie:磁盘 Cache 可选(默认关闭),遵循现代 HTTP 缓存语义;CookieJar 负责读写 cookie。

  • 安全(HTTPS) :支持 TLS1.3/ALPN、ConnectionSpec、HostnameVerifier、CertificatePinner;可按需降级/自定义套件。

  • 可观测性:EventListener 按阶段发事件(DNS、连接获取/释放、缓存命中等),用于埋点与排障。

2)一次请求是怎样跑起来的(从newCall()到复用连接)

  1. 你构造 Request 并 client.newCall(request)。

  2. 拦截器链按顺序执行:应用拦截器 →(可能有缓存命中)→ 网络拦截器 → 发送请求/读取响应。链上每个节点都通过 chain.proceed() 传递下去。

  3. 选连接:先用 URL + Client 配成 Address,再从 ConnectionPool找可复用连接;找不到就挑一条 Route(DNS/代理/TLS 版本),新建连接并握手。请求结束后把连接放回池子。

  4. HTTP/2 多路复用:同一连接可跑多个并发流;HTTP/1.1 要等响应体读完并关闭后才能复用。

  5. 并发控制:异步请求进入 Dispatcher 的等待/运行队列,受 maxRequests / maxRequestsPerHost 约束。

  6. 故障恢复与回退:重定向/认证质询/连接失败会自动重试;在 OkHttp 5 中还有 Fast Fallback(Happy Eyeballs) ,并发竞速 IPv6/IPv4,谁先连通用谁。

小细节:要让连接进入可复用状态,务必把响应体读完并关闭(否则不会回到连接池)。

3)默认策略为何如此(背后的工程取舍)

  • Client 复用:单一 OkHttpClient + newBuilder() 派生,能共享连接池/线程池 → 更高命中率、更低延迟与内存占用。

  • 拦截器分层:应用拦截器屏蔽中间响应(重定向/重试),关注“应用意图”;网络拦截器贴近“线上传输”,能看到压缩/连接信息——这样的分层让功能解耦、可测试。

  • 并发默认值(64 / 5) :对多数客户端既能避免过度并发占用资源,又兼顾 HTTP/2 的并发流;需要时再按场景调高。

  • 连接抽象(URL/Address/Route) :把静态与动态要素拆开,便于复用容错(多 IP、代理认证、TLS 协商失败时的回退)。

  • 缓存:遵循现代 HTTP 规范,默认关闭避免误用;开启后用事件流能直观看到命中/条件命中等路径。

  • 安全:默认现代 TLS,必要时按 ConnectionSpec 回退;官方建议保持库版本更新以适应 TLS 生态变化。

4)什么时候该自定义(以及怎么改)

  • 高并发单域名 / 短连接多:适当提高 Dispatcher.maxRequests / maxRequestsPerHost;不要指望连接池限制并发,它只管空闲连接。

  • 需要端到端观测:接入 EventListener 记录 DNS、连接获取/释放、缓存命中与失败原因。

  • 网络复杂/跨 IPv6/IPv4:升级到 5.x,默认有 Fast Fallback 的更快建连体验。

  • HTTPS 特殊要求:通过 connectionSpecs、CertificatePinner、HostnameVerifier 精细化配置。

  • 缓存策略:为离线/省流量场景开启 Cache,并结合服务端的 Cache-Control;调试时看文档中的“Cache Events”。

5)一段“拿来就用”的配置模板

val client = OkHttpClient.Builder()
  // 可观察:埋点全链路事件
  .eventListener(object : EventListener() {/* override想看的事件 */})
  // 应用拦截器(重试、鉴权、日志等)
  .addInterceptor( /* your app interceptor */ )
  // 可选:网络拦截器(只做与网络强相关的逻辑)
  //.addNetworkInterceptor(/* e.g. HttpLoggingInterceptor() */)
  // 缓存(可选)
  //.cache(Cache(File(cacheDir, "http_cache"), 50L * 1024 * 1024))
  // 并发策略
  .dispatcher(Dispatcher().apply {
    maxRequests = 64
    maxRequestsPerHost = 10
  })
  // 连接/HTTPS 策略(示例)
  //.connectionSpecs(listOf(ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS))
  // 超时(连接默认 10s,可按需调整)
  .connectTimeout(10, java.util.concurrent.TimeUnit.SECONDS)
  .build()

(并发与缓存配置参考官方 API/文档;连接/HTTPS 策略与默认连接超时见对应条目。 )