一、定位
OkHttp 是 Android/Java 世界事实标准的高性能 HTTP 客户端,负责真正的网络收发。它的三大核心能力:拦截器责任链(请求/响应的分层加工)、连接池复用(TCP/TLS 握手成本摊销)、现代协议支持(HTTP/2 多路复用、WebSocket、HTTP/1.1 keep-alive)。 Retrofit 管"接口抽象",OkHttp 管"连接与传输"——Retrofit 一次网络 IO 都不做,全部交给 OkHttp。
二、基本用法与角色
val client = OkHttpClient.Builder()
.connectTimeout(15, SECONDS)
.addInterceptor(MyInterceptor()) // 应用拦截器
.build() // 全局单例!(连接池/线程池共享)
val request = Request.Builder().url("https://api.example.com/user/1").build()
client.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) { ... }
override fun onFailure(call: Call, e: IOException) { ... }
})
四个核心角色:
| OkHttpClient | 配置中心 + Call 工厂。必须全局单例——连接池、Dispatcher 线程池、缓存都挂在它上面,多实例会导致资源无法复用 |
| Request | 不可变的请求描述(URL、方法、头、体),建造者模式 |
| Call(RealCall) | 一次请求的执行体,只能执行一次(executed 标志防重入) |
| Dispatcher | 异步调度器,管理并发上限和线程池 |
三、核心工作流程(源码主线)
client.newCall(request) → 创建 RealCall
│
├─ execute() → 同步:dispatcher.executed() 登记
│ → getResponseWithInterceptorChain()
│
└─ enqueue(callback) → 异步:dispatcher.enqueue(AsyncCall)
→ 线程池执行 → 同样走 getResponseWithInterceptorChain()
主入口 RealCall.getResponseWithInterceptorChain():
// 源码核心(简化):组装拦截器列表
val interceptors = mutableListOf<Interceptor>()
interceptors += client.interceptors // ① 用户自定义应用拦截器
interceptors += RetryAndFollowUpInterceptor // ② 重试与重定向
interceptors += BridgeInterceptor // ③ 桥接:补全请求头
interceptors += CacheInterceptor // ④ 缓存
interceptors += ConnectInterceptor // ⑤ 建立连接
if (!forWebSocket) {
interceptors += client.networkInterceptors // ⑥ 用户自定义网络拦截器
}
interceptors += CallServerInterceptor // ⑦ 真正读写网络(链尾)
// 从 index=0 开始,责任链递归下传
val chain = RealInterceptorChain(interceptors, index=0, ...)
return chain.proceed(request)
责任链精髓:每个拦截器拿到 chain,做三件事——加工请求 → 调 chain.proceed() 向下传递 → 拿到下游响应后加工响应。proceed() 内部是 RealInterceptorChain(index+1) 递归,所以请求是"正序进入",响应是"倒序流出",形成典型的洋葱模型。
四、五大内置拦截器详解
4.1 RetryAndFollowUpInterceptor —— 重试与重定向
- 循环 while(true) 发起请求,根据响应决定下一步:
- 3xx 重定向:读取 Location 构建新请求继续 follow(上限 20 次,防死循环)
- 401/407 认证失败:调 Authenticator 获取凭证(比如刷新 Token),重放请求——这就是 Retrofit 实战里 Token 自动刷新的挂载点
- 连接失败:判断是否可恢复(recoverable,如连接池拿到坏连接),可恢复则换路重试
- 不可恢复或超限才把响应/异常抛回上游
4.2 BridgeInterceptor —— 应用层与网络层的桥
补全"应用开发者不关心、但 HTTP 协议必须"的细节:
- 请求侧:自动加 Content-Length、Content-Type、Host、Connection: Keep-Alive、Accept-Encoding: gzip、User-Agent、Cookie(从 CookieJar 读)
- 响应侧:透明 gzip 解压(服务器返回 gzip 而你没手动指定时,自动解压并剥掉 Content-Length/Content-Encoding)、Set-Cookie 写入 CookieJar
问题:为什么在网络拦截器里看到的 Content-Length 和最终一致、而应用拦截器里可能还没有?——因为补全发生在 Bridge,应用拦截器在 Bridge 之前。
4.3 CacheInterceptor —— HTTP 缓存策略
核心:CacheStrategy.Factory 计算出一个策略组合(networkRequest / cacheResponse 两个值):
| networkRequest=null, cacheResponse≠null | 强缓存命中:不发网络,直接返回缓存响应(Cache-Control: max-age 未过期) |
| networkRequest≠null, cacheResponse=null | 只用网络(无缓存或缓存被禁) |
| 两者都≠null | 协商缓存:发条件请求(If-None-Match: ETag / If-Modified-Since),服务器回 304 则合并缓存 body,省流量 |
| 两者都 null | 返回 504 Unsatisfiable Request |
- 需要 OkHttpClient.Builder().cache(Cache(dir, size)) 显式开启,否则此拦截器直接透传
- 只缓存 GET;缓存头由服务器 Cache-Control 驱动,客户端可用 request.cacheControl() 强制(如 FORCE_CACHE、only-if-cached 离线模式)
4.4 ConnectInterceptor —— 建立连接(最重的一环)
这一层只做一件事:给请求搞到一条可用的 RealConnection。
ExchangeFinder.findConnection()
│
├─ ① 复用当前 Call 已持有的连接
├─ ② 查连接池 ConnectionPool:存在 Address 完全匹配
│ (host、port、协议、DNS、代理、SSL 配置全部相同)
│ 且空闲的连接 → 直接复用
│ ├─ HTTP/1.1:复用 keep-alive 的 socket
│ └─ HTTP/2:同一条 TCP 上开新 Stream(多路复用)
│
└─ ③ 池里没有 → new RealConnection → connect():
TCP 三次握手 → TLS 握手(HTTPS)→ ALPN 协商协议(h2 / http1.1)
→ 放入连接池供后续复用
连接池机制:
- ConnectionPool(内部 RealConnectionPool)持有连接队列,每连接默认空闲保活 5 分钟,后台 cleanup 任务定期清理过期连接
- 复用判断靠连接的引用计数(allocations):HTTP/1.1 同一时刻只能承载一个流;HTTP/2 可并发多流(上限由 SETTINGS 帧协商)
- 意义:TCP 握手 + TLS 握手是百毫秒级成本,池化复用是 OkHttp "快"的第一来源
4.5 CallServerInterceptor —— 链尾,真正的网络 IO
- 写请求行 + 请求头 → 写请求体(流式:okio Buffer 分块写,不把整个文件读进内存)
- 读响应行(HTTP/1.1 200 OK)→ 读响应头 → 构造 Response
- 响应体同样是流式返回——所以 response.body.string() 只能调一次(读完流就关了),大文件必须 byteStream() 分块写盘
五、自定义拦截器:两种
| 位置 | 链最前端(RetryAndFollowUp 之前) | Connect 与 CallServer 之间 |
| 触发次数 | 一次请求只触发一次(重定向不重入) | 每次实际网络交互都触发(重定向/重试可见) |
| 能看到 | 原始请求(可能还没补 Host/gzip) | 传输层的真实请求/响应 |
| 典型用途 | Token 注入、全局日志、Mock 数据、加解密 | 观察重定向、精确统计网络耗时 |
六、Dispatcher:异步调度
- 三个队列:readyAsyncCalls(等待)、runningAsyncCalls(执行中)、runningSyncCalls(同步)
- 并发上限:全局最大 64 个请求,单 host 最大 5 个(maxRequests/maxRequestsPerHost,可改)
- 线程池:SynchronousQueue + 0 核心线程 + Integer.MAX_VALUE 上限 + 60s 空闲回收——类似 newCachedThreadPool,任务即来即跑、用完即释放
- 回调线程:enqueue 的 onResponse 跑在 OkHttp 的 Dispatcher 线程池,不是主线程——裸用 OkHttp 更新 UI 必须自己切线程(这就是 Retrofit 帮你加 MainThreadExecutor 的原因)
七、其他核心能力
| HTTP/2 | 单 TCP 连接多路复用,头部压缩(HPACK),服务端推送;OkHttp 自动 ALPN 协商,无需手动开 |
| WebSocket | newWebSocket(request, listener),长连接双向通信,绕过部分拦截器 |
| 超时体系 | connectTimeout / readTimeout / writeTimeout / callTimeout(整次调用总时限),粒度比 HttpClient 细 |
| 取消 | call.cancel() → 连接层中断 exchange,协程场景 Retrofit 的 suspendCancellableCoroutine 就是联动它 |
| 流式 IO | 基于 okio(Square 自研 IO 库),Buffer 链式分块,避免大数组拷贝 |
| 请求体进阶 | RequestBody.create() 子类化可做上传进度回调(包装 sink 统计写入字节) |
八、完整的流程
以 client.newCall(request).enqueue(callback) 为起点,按真实时间顺序走一遍——包括上一轮没展开的 socket 层细节。
阶段 1:创建 Call
client.newCall(request)
└── new RealCall(client, request, forWebSocket=false)
└── 此时只是包装了配置,executed=false,什么都没发生
阶段 2:调度
异步路线(enqueue):
RealCall.enqueue(callback)
├── check(!executed) → executed = true // 一个 Call 只能执行一次
└── dispatcher.enqueue(AsyncCall)
├── 判断:runningAsyncCalls < 64 且 同host运行数 < 5
│ ├── 是 → 移入 runningAsyncCalls,交给线程池执行
│ └── 否 → 放入 readyAsyncCalls 等待队列
└── executorService.execute(AsyncCall) // SynchronousQueue 缓存线程池
└── AsyncCall.run()
└── getResponseWithInterceptorChain() ← 进入正题
同步路线(execute):
跳过 Dispatcher 队列,在当前线程直接调 getResponseWithInterceptorChain()(仅登记 runningSyncCalls)。
阶段 3:进入责任链
getResponseWithInterceptorChain()
└── 组装拦截器列表 → RealInterceptorChain(index=0).proceed(request)
└── 递归驱动:每个拦截器 intercept(chain) 内部再调 chain.proceed(index+1)
请求开始沿链正序下钻:
阶段 4:请求下钻(逐层加工)
① 应用拦截器(你的 Token/日志拦截器)
│ chain.proceed()
▼
② RetryAndFollowUpInterceptor
│ 开启 while(true) 循环(重试/重定向时请求会从这里重新发出)
│ chain.proceed()
▼
③ BridgeInterceptor
│ 补全:Host、Content-Type、Content-Length、
│ Connection: Keep-Alive、Accept-Encoding: gzip、Cookie、User-Agent
│ chain.proceed()
▼
④ CacheInterceptor
│ ├─ 强缓存命中?→ 直接返回缓存 Response(请求终止,倒序流出)
│ └─ 否则放行(可能带上 If-None-Match 做协商)
│ chain.proceed()
▼
⑤ ConnectInterceptor —— 拿到一条可用连接(见阶段 5)
│ chain.proceed()
▼
⑥ 网络拦截器(可观察到真实的传输层数据)
│ chain.proceed()
▼
⑦ CallServerInterceptor —— 真正的网络 IO(见阶段 6)
阶段 5:ConnectInterceptor 内部——连接怎么来的
ExchangeFinder.findConnection()
│
├─ 查当前 Call 已分配的连接是否可复用
├─ 遍历 RealConnectionPool.connections:
│ Address 全匹配(host/port/协议/DNS/代理/SSL配置)
│ && 引用计数空闲(HTTP/1.1 无占用流;HTTP/2 未达流上限)
│ → 命中:返回已有连接(零握手成本)
│
└─ 未命中 → new RealConnection() → connect():
│
├─ 1. 建立 TCP:socket.connect(address, connectTimeout)
│ → 内核三次握手
├─ 2. TLS 握手(HTTPS):SSLSocket 握手、证书校验(信任链+ hostname 验证)
├─ 3. ALPN 协议协商:服务器说 h2 → HTTP/2;否则 http/1.1
│ ├─ h2:启动 Http2Connection 读写线程,发 SETTINGS 帧
│ └─ http/1.1:保持 socket 即可
└─ 4. 连接入池,供本次及后续请求复用
阶段 6:CallServerInterceptor 内部——数据怎么发出去
Exchange(由 Connect 阶段创建,持有 codec + connection)
│
├─ 写请求行:GET /user/1 HTTP/1.1\\r\\n
├─ 写请求头:逐行写出(Host/Accept/…)
├─ 写请求体:RequestBody.writeTo(sink)
│ → okio Buffer 分块 → socket.getOutputStream() → 内核 TCP 发送缓冲区
│ → flushRequest() 确保全部发出
│
├─ readResponseHeaders():
│ 读响应行(HTTP/1.1 200 OK)→ 解析状态码
│ 读响应头 → 构建 Response(此时 body 还没读)
│
└─ openResponseBody():挂上流式读取器(不预读内容)
注意此时:Response 对象已经生成,但响应体还"躺"在 socket 里——只有调用 body.string()/byteStream() 时才真正从网络流里读数据。这就是大文件不会 OOM 的原因。
阶段 7:响应倒序流出(回程加工)
⑦ CallServer 返回 Response
▲
⑥ 网络拦截器(观察真实响应)
▲
⑤ Connect(无操作,透传)
▲
④ CacheInterceptor:响应可缓存?→ 写入磁盘缓存(异步)
│ 协商请求收到 304?→ 合并缓存 body 返回
▲
③ BridgeInterceptor:gzip 透明解压、Set-Cookie 存入 CookieJar
▲
② RetryAndFollowUp:判定响应码
│ ├─ 3xx → 构建新请求,while 循环重新下钻(阶段 4)
│ ├─ 401 → 调 Authenticator 刷新凭证,重放
│ └─ 2xx/其他 → 放行给上游
▲
① 应用拦截器(拿到最终响应,可做全局解密封装)
▲
回到 getResponseWithInterceptorChain() 返回 Response
阶段 8:交付与收尾
AsyncCall.run() 收尾:
├── callback.onResponse(call, response) // Dispatcher 线程池!非主线程
│ └── 你在这里读 body → 真正触发网络流的读取
│
├── finally 里 dispatcher.finished()
│ → runningAsyncCalls 移除本任务
│ → promoteAndExecute():从 readyAsyncCalls 捞下一个等待任务执行
│
└── 连接归还:Exchange 释放对 RealConnection 的引用
→ 引用计数归零 → 连接回到池中空闲状态(保活 5 分钟,等待复用)
九、全流程一图速记
enqueue()
│ Dispatcher 限流调度(64/5,缓存线程池)
▼
RealInterceptorChain
│ ① 应用拦截器 ─────────────────────────────┐
▼ │
② RetryAndFollowUp(while 循环) │
▼ │
③ Bridge 补全请求头 │ 响
▼ │ 应
④ Cache(强缓存可直接终结请求) │ 倒
▼ │ 序
⑤ Connect:池里找 → 没有则 TCP握手→TLS→ALPN │ 流
▼ │ 出
⑥ 网络拦截器 │ (Cache写盘、
▼ │ Bridge解压、
⑦ CallServer:写请求 → socket → 读响应头 │ Retry判定)
│ │
└──────────── Response 生成 ────────────────┘
▼
onResponse(Dispatcher 线程池,读 body 才真正读网络流)
▼
任务出队 → 捞下一个等待任务 → 连接归还池中保活
完整流程:请求经 newCall 包装成 RealCall,enqueue 后由 Dispatcher 按"全局 64、单 host 5"限流,丢进缓存线程池;执行时进入责任链——应用拦截器加工、RetryAndFollowUp 开启重试循环、Bridge 补全 Host/gzip 等协议头、Cache 判定能否直接命中、Connect 从连接池找 Address 匹配的空闲连接(找不到就 TCP 三次握手+TLS 握手+ALPN 协商新建并入池)、最后 CallServer 用 okio 流式写出请求并读取响应头生成 Response;响应倒序流出时 Cache 写盘、Bridge 解压、Retry 判定重定向或 401 重放;最终回调在 Dispatcher 线程池交付,连接归还池中保活复用。真正的 body 数据在你调用 body.string() 时才从流中读出。


![[Android] 即存 -10+平台视频下载-171主机测评](https://www.171host.com/wp-content/uploads/2026/10/20261029031849-6ae2bb19c8977-220x150.jpg)



