Okhttp源码分析¶
这篇笔记保留了早期 Java/Kotlin 源码摘录。新增的设计模式分析与 UML统一对照 OkHttp 4.9.3,重点解释对象的职责、协作与模式边界;旧摘录中的简化表述可结合文末的阅读提醒对照。
基本流程¶
- 创建
RequestBody - 创建
Request - 创建
OkhttpClient - 调用
newCall创建Call对象 - 执行异步或同步操作。
- 创建
Socket连接 - 发送请求并处理返回结果
创建RequestBody¶
RequestBody主要通过writeTo方法将请求内容写入到BufferedSink。RequestBody提供了3个create静态方法来创建RequestBody,此外RequestBody还包含两个子类。
FormBody用于表单提交,MultipartBody用于多内容提交。
创建Request¶
Request对象包含如下信息
创建OkHttpClient¶
OkHttpClient可配置的信息包括如下:
调用newCall创建Call对象¶
OkHttpClient继承Call.Factory。
OkHttpClient的newCall方法调用
执行网络请求¶
Call支持执行网络请求的方法enqueue和execute()。enqueue执行异步请求,execute()执行同步请求。
enqueue()¶
Dispatcher类
promoteAndExecute()¶
executeOn()¶
AsyncCall的executeOn方法
拦截器¶
RealInterceptorChain的proceed会创建一个InterceptorChain,并调用拦截器的intercept方法。除了CallServerInterceptor外,所有拦截器的intercept都会调用创建的InterceptorChain的proceed方法。
getResponseWithInterceptorChain()¶
proceed()¶
缓存分析¶
Cache¶
缓存核心类是Cache对象。
Cache的构造函数。
Cache提供了两个方法用于存储和获取缓存。
put方法将缓存存储到磁盘上。
get方法获取缓存
CacheInterceptor¶
Cache这两个方法都被CacheInterceptor对象调用。所以处理缓存的核心逻辑都在CacheInterceptor中。
缓存逻辑¶
isCacheable方法用来判断是否可以缓存。
这里面加了一堆判断,这里需要先了解一下HTTP的缓存原理。才能搞清楚这些判断的逻辑。
Http缓存原理¶
在HTTP 1.0时代,响应使用Expires头标识缓存的有效期,其值是一个绝对时间,比如Expires:Thu,31 Dec 2020 23:59:59 GMT。当客户端再次发出网络请求时可比较当前时间 和上次响应的expires时间进行比较,来决定是使用缓存还是发起新的请求。
使用Expires头最大的问题是它依赖客户端的本地时间,如果用户自己修改了本地时间,就会导致无法准确的判断缓存是否过期。
因此,从HTTP 1.1 开始使用Cache-Control头表示缓存状态,它的优先级高于Expires,常见的取值为下面的一个或多个。
- private,默认值,标识那些私有的业务逻辑数据,比如根据用户行为下发的推荐数据。该模式下网络链路中的代理服务器等节点不应该缓存这部分数据,因为没有实际意义。
- public 与private相反,public用于标识那些通用的业务数据,比如获取新闻列表,所有人看到的都是同一份数据,因此客户端、代理服务器都可以缓存。
- no-cache 可进行缓存,但在客户端使用缓存前必须要去服务端进行缓存资源有效性的验证,即下文的对比缓存部分,我们稍后介绍。
- max-age 表示缓存时长单位为秒,指一个时间段,比如一年,通常用于不经常变化的静态资源。
- no-store 任何节点禁止使用缓存。
强制缓存¶
在上述缓存头规约基础之上,强制缓存是指网络请求响应header标识了Expires或Cache-Control带了max-age信息,而此时客户端计算缓存并未过期,则可以直接使用本地缓存内容,而不用真正的发起一次网络请求。
协商缓存¶
强制缓存最大的问题是,一旦服务端资源有更新,直到缓存时间截止前,客户端无法获取到最新的资源(除非请求时手动添加no-store头),另外大部分情况下服务器的资源无法直接确定缓存失效时间,所以使用对比缓存更灵活一些。
使用**Last-Modify** / **If-Modify-Since**头实现协商缓存,具体方法是服务端响应头添加Last-Modify头标识资源的最后修改时间,单位为秒,当客户端再次发起请求时添加If-Modify-Since头并赋值为上次请求拿到的Last-Modify头的值。
服务端收到请求后自行判断缓存资源是否仍然有效,如果有效则返回状态码304同时body体为空,否则下发最新的资源数据。客户端如果发现状态码是304,则取出本地的缓存数据作为响应。
使用这套方案有一个问题,那就是资源文件使用最后修改时间有一定的局限性:
- Last-Modify单位为秒,如果某些文件在一秒内被修改则并不能准确的标识修改时间。
- 资源修改时间并不能作为资源是否修改的唯一依据,比如资源文件是Daily Build的,每天都会生成新的,但是其实际内容可能并未改变。
建立连接¶
OkHttp通过ConnectInterceptor建立连接。在ConnectInterceptor的intercept方法中,通过调用RealCall的initExchange方法初始化一个Exchange对象。Exchange对象负责交换数据。
initExchange()¶
find()¶
findHealthyConnection()¶
findConnection()¶
- 如果call里面的connection不为空,则复用call里面的connection。
- 如果call中的connection为空,则从连接池中获取一个。
- 如果缓存池里也没有就创建一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | |
OkHttp连接复用¶
OkHttp 连接复用主要是通过 RealConnectionPool 来实现的,其内部定义了一个 ConcurrentLinkedQueue 来存储创建的 RealConnection。当获取连接时,优先判断 Call 中的` Connection是否为空,如果不为空则直接复用,为空则从连接池中获取,当从连接池中获取不到的时候才调用RealConnection构造函数创建一个新的连接并添加到 RealConnectionPool 中。RealConnectionPool 提供了两个可配置的参数:最大空闲连接数和存活时长,这两个参数默认是 5 个和 5 分钟,当然我们也可以在OKHttpClient 中自己配置。当向 RealConnectionPool 中添加连接和连接变成空闲时,内部都会遍历队列中所有的连接,如果最大空闲连接数或者存活时长都大于设定的则移除存空闲时长最长的连接,该方法会多次执行,直到最大空闲连接数和存活时长都小于设定的值。
ConnectionPool¶
清理连接¶
执行网络请求¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | |
参考¶
设计模式分析¶
本节对照 OkHttp 4.9.3 固定版本源码分析:parent-4.9.3,提交 0a27e9ff30c6129f4fdeb1f99c07c9b009bcc537。旧文保留的 Java/Kotlin 摘录不保证来自同一个版本;以下图中的类、字段和调用关系统一以这个版本为准。
UML 省略无关成员、泛型和异常;internal 成员用 ~ 简化表示模块内可见,嵌套类会标出外层名称。涉及 JDK、Okio 或可选模块的类型会单独说明,不虚构源码中不存在的角色类。
| 模式或设计思想 | 具体落点 | 解决的问题 |
|---|---|---|
| 建造者 | OkHttpClient.Builder、Request.Builder |
分步组织可选配置,构造客户端和请求。 |
| 工厂方法 | Call.Factory.newCall()、OkHttpClient |
把调用对象的创建隐藏在统一接口之后。 |
| 责任链 | Interceptor、RealInterceptorChain |
按顺序组合请求处理,并允许短路或重试下游。 |
| 策略 | Dns、DnsSystem、DnsOverHttps |
在路由流程不变的情况下替换域名解析算法。 |
| 命令思想 | RealCall.AsyncCall、Dispatcher |
把执行动作封装成可排队、调度的对象。 |
| 装饰者 | Exchange.RequestBodySink、ResponseBodySource |
在数据流读写外增加计数、校验和完成通知。 |
| 原型 | Call.clone()、RealCall.clone() |
从已有调用描述创建一个新的调用状态实例。 |
| 观察者式事件通知 | EventListener.Factory、EventListener |
将网络生命周期监控与请求实现分离。 |
建造者:分别组装客户端和请求¶
OkHttpClient 的配置包括连接池、调度器、拦截器、DNS、缓存、超时等;Request 则需要 URL、方法、请求头和请求体。Builder 将这些输入的收集过程与最终对象创建分开,避免调用者面对过长的构造参数列表。
| 类 | 模式角色 | 职责 |
|---|---|---|
OkHttpClient.Builder |
客户端的具体建造者 | 提供默认配置及 addInterceptor()、dns()、connectTimeout() 等配置入口。 |
OkHttpClient |
客户端产品 | 保存配置和共享组件,作为后续创建 Call 的入口。 |
Request.Builder |
请求的具体建造者 | 收集 URL、方法和请求体等信息,校验后创建 Request。 |
Request |
请求产品 | 描述一次请求;还未包含执行状态和网络连接。 |
classDiagram
direction TB
class ClientBuilder["OkHttpClient.Builder"] {
+addInterceptor(Interceptor interceptor) Builder
+dns(Dns dns) Builder
+connectTimeout(long timeout, TimeUnit unit) Builder
+build() OkHttpClient
}
class OkHttpClient {
+newBuilder() Builder
+newCall(Request request) Call
}
class RequestBuilder["Request.Builder"] {
+url(String url) Builder
+method(String method, RequestBody body) Builder
+build() Request
}
class Request {
+newBuilder() Builder
}
ClientBuilder ..> OkHttpClient : build 创建
OkHttpClient ..> ClientBuilder : newBuilder 复制配置
RequestBuilder ..> Request : build 创建
Request ..> RequestBuilder : newBuilder 复制描述
OkHttpClient ..> Request : newCall 参数
调用者按需设置 Builder,最后调用 build()。Request.Builder.build() 检查 URL 是否缺失;方法与请求体是否匹配等条件在配置方法中校验。OkHttpClient.Builder 提供默认组件,构造出的客户端会复制拦截器列表并完成 TLS 等配置的初始化和检查。
newBuilder() 可从已有对象继续配置:派生客户端默认共享原来的 Dispatcher、连接池等组件;请求的新 Builder 复制请求头等结构,却不会深复制 RequestBody 或 tag 对象。Builder 的意义是组织构建过程,并不是承诺构造整个对象图的独立副本。这里也没有专门的 Director 类,配置步骤由应用代码组织。
源码:OkHttpClient 与 Builder、Request 与 Builder。
工厂方法:通过 Call.Factory 创建调用¶
使用方只要依赖 Call.Factory,就能根据 Request 获得 Call,无须知道 RealCall 的构造细节。OkHttpClient 实现 newCall(),返回与自身配置关联的新 RealCall。
| 类或接口 | 模式角色 | 职责 |
|---|---|---|
Call.Factory |
创建者接口 | 定义 newCall(Request),允许使用方注入不同创建实现。 |
OkHttpClient |
具体创建者 | 用当前客户端、原始请求和普通 HTTP 标志构造 RealCall。 |
Call |
产品接口 | 对外提供执行、取消、查询状态与克隆等操作。 |
RealCall |
具体产品 | 保存请求及调用状态,并组织真正的执行过程。 |
Request |
创建参数 | 向工厂描述要执行什么请求。 |
classDiagram
direction TB
class CallFactory["Call.Factory"] {
<<interface>>
+newCall(Request request) Call
}
class OkHttpClient {
+newCall(Request request) Call
}
class Call {
<<interface>>
+execute() Response
+enqueue(Callback callback)
+cancel()
}
class RealCall {
+OkHttpClient client
+Request originalRequest
}
class Request
CallFactory <|.. OkHttpClient
Call <|.. RealCall
CallFactory ..> Call : 返回抽象产品
OkHttpClient ..> RealCall : 创建
RealCall --> OkHttpClient : client
RealCall --> Request : originalRequest
newCall() 每次创建独立的调用对象,此时不会发送 HTTP 请求。网络工作由之后的 execute() 或 enqueue() 启动。一个客户端可以复用配置与资源,却可以创建许多独立 Call。
这是通过实现创建者接口提供工厂方法的形式,不依赖一个包含大量业务步骤的抽象父类。Call.Factory 是嵌套接口,OkHttpClient 实现它,并不继承 Call。这里创建的是一种产品抽象 Call,也不能因客户端还支持 WebSocket 创建就直接认定为抽象工厂。
源码:Call.Factory 与 Call 契约、OkHttpClient.newCall()、RealCall 的构造与执行入口。
责任链:拦截器共同完成请求与响应处理¶
拦截器接收 Interceptor.Chain,可以在调用 proceed(request) 前修改请求,在其返回后检查或包装响应,也可以在允许的阶段直接返回响应。RealInterceptorChain 保存拦截器列表和当前位置,并为下一步创建新的链上下文。
| 类或接口 | 模式角色 | 职责 |
|---|---|---|
Interceptor |
处理者接口 | 通过 intercept(chain) 处理请求和响应。 |
Interceptor.Chain |
链调用接口 | 暴露当前请求与 proceed(),连接后续处理流程。 |
RealInterceptorChain |
分派上下文 | 保存 interceptors、index、请求及 Exchange 等状态,调用当前位置的拦截器。 |
CacheInterceptor |
具体处理者 | 决定直接使用缓存、条件验证或继续访问网络。 |
CallServerInterceptor |
末端处理者 | 通过 Exchange 写入请求并读取响应,不继续分派下一个拦截器。 |
RealCall |
流程发起者 | 组装完整链并发起第一次 proceed()。 |
classDiagram
direction TB
class RealCall {
~getResponseWithInterceptorChain() Response
}
class Chain["Interceptor.Chain"] {
<<interface>>
+request() Request
+proceed(Request request) Response
}
class RealInterceptorChain {
-List interceptors
-int index
~copy() RealInterceptorChain
+proceed(Request request) Response
}
class Interceptor {
<<interface>>
+intercept(Interceptor.Chain chain) Response
}
class CacheInterceptor {
+intercept(Interceptor.Chain chain) Response
}
class CallServerInterceptor {
+intercept(Interceptor.Chain chain) Response
}
Chain <|.. RealInterceptorChain
Interceptor <|.. CacheInterceptor
Interceptor <|.. CallServerInterceptor
RealCall ..> RealInterceptorChain : 组装并启动
RealInterceptorChain --> "1..*" Interceptor : 按 index 调用
Interceptor ..> Chain : 接收并按需继续
完整顺序是:应用拦截器 → 重试与重定向 → Bridge → Cache → Connect → 网络拦截器 → CallServer。图中仅展开缓存和末端拦截器,省略其他实现;WebSocket 调用不会加入用户网络拦截器。
proceed() 使用 copy(index = index + 1, request = request) 创建下一个链对象,再调用当前 interceptor.intercept(next)。请求沿调用栈向后传递,响应随返回过程逐层回到前面的拦截器。具体拦截器不保存 next 字段,后继位置由 Chain 管理。
这不是“必须每层调用一次”的统一规则:
- **应用拦截器**可以短路,也可以多次调用下游;再次调用之前必须关闭前一次响应体。
- **缓存拦截器**命中可用缓存时可以直接返回;禁止网络且无可用缓存时也会直接返回 504。并非除了 CallServer 以外的所有拦截器都必然调用
proceed()。 - **重试与重定向拦截器**可能多次进入下游,因此一次 Call 可以包含多次网络交换。
- **网络拦截器**受到 Exchange 阶段的约束:必须保持同一主机和端口,并且恰好调用一次
proceed();RealInterceptorChain会检查这些条件。
和 Retrofit 的工厂列表查找相比,那里通常是“首个匹配者返回产品”;这里多个处理者可以参与同一次请求,并在响应返回时继续处理。责任链可以有不同实现形态,不能仅凭一个循环或类名判断它们完全相同。
源码:链的组装、RealInterceptorChain.proceed()、CacheInterceptor、RetryAndFollowUpInterceptor、CallServerInterceptor。
策略:替换 DNS 解析算法¶
路由选择需要一组候选 IP 地址,但不应把具体解析方式写死。OkHttp 用 Dns.lookup(hostname) 表达这个变化点:系统 DNS 和 DNS over HTTPS 都提供同一个契约,路由代码只消费解析结果。
| 类或接口 | 模式角色 | 职责 |
|---|---|---|
Dns |
策略接口 | 根据主机名返回按尝试顺序排列的 IP 地址列表。 |
DnsSystem |
默认具体策略 | Dns 伴生对象中的私有实现,通过 InetAddress.getAllByName() 解析。 |
DnsOverHttps |
可选具体策略 | 来自同仓库的 okhttp-dnsoverhttps 模块,通过 HTTPS 查询 DNS。 |
Address |
策略的持有者 | 保存当前目标地址所使用的 Dns 等配置。 |
RouteSelector |
策略的使用上下文 | 需要本地解析时调用 address.dns.lookup(),据结果构造候选套接字地址。 |
OkHttpClient |
配置入口 | 从 Builder 接收 Dns;由 RealCall.createAddress() 将 client.dns 传入 Address。 |
classDiagram
direction TB
class OkHttpClient {
+Dns dns
}
class Address {
+Dns dns
}
class RouteSelector {
-Address address
}
class Dns {
<<interface>>
+lookup(String hostname) List
}
class DnsSystem {
+lookup(String hostname) List
}
class DnsOverHttps {
+lookup(String hostname) List
}
OkHttpClient --> Dns : 配置策略
Address --> Dns : 保存策略
RouteSelector --> Address : address
RouteSelector ..> Dns : lookup
Dns <|.. DnsSystem
Dns <|.. DnsOverHttps
应用通过 OkHttpClient.Builder.dns() 选择策略,默认是 Dns.SYSTEM。在需要本地解析的连接路径中,RouteSelector 发出 DNS 生命周期事件,调用策略取得 IP 列表,再交给后续连接尝试。SOCKS 代理使用未解析的套接字地址,由代理处理解析,不能推断所有请求都会走本地 Dns.lookup()。
改变 DNS 实现不需要改写连接流程,但替代实现仍需满足线程安全和返回值契约。DnsOverHttps 属于可选模块,不是核心客户端默认启用的策略。认证器 Authenticator、Cookie 存储 CookieJar 也提供类似的可替换行为接口。
源码:Dns 与 DnsSystem、RouteSelector 的解析分支、Address、DnsOverHttps。
命令思想:把异步请求变为可调度的任务¶
Call 将请求描述与执行状态封装成对象;异步路径进一步创建 RealCall.AsyncCall,通过 Runnable 把“执行请求并交付回调”的动作交给调度器与线程池。调用的提交和真正执行因此可以分离。
| 类或接口 | 对应角色 | 职责 |
|---|---|---|
Runnable |
可执行命令接口 | JDK 提供的 run() 契约。 |
RealCall.AsyncCall |
具体命令对象 | 保存回调并访问外层 RealCall,执行拦截器链、交付结果和通知调度完成。 |
Dispatcher |
排队和调度者 | 维护待执行、执行中队列及异步并发限制,选择任务提交。 |
ExecutorService |
实际执行者 | 接收 Runnable,在线程池中运行任务。 |
RealCall |
执行逻辑的承载者 | 保存请求与调用状态,提供拦截器链执行入口。 |
Callback |
结果接收接口 | 接收响应或失败通知,不是用于网络各阶段监控的 EventListener。 |
classDiagram
direction TB
class Dispatcher {
-ArrayDeque readyAsyncCalls
-ArrayDeque runningAsyncCalls
~enqueue(AsyncCall call)
-promoteAndExecute() boolean
~finished(AsyncCall call)
}
class Runnable {
<<interface>>
+run()
}
class AsyncCall["RealCall.AsyncCall"] {
-Callback responseCallback
+executeOn(ExecutorService executorService)
+run()
}
class ExecutorService {
<<interface>>
+execute(Runnable task)
}
class RealCall {
+enqueue(Callback callback)
~getResponseWithInterceptorChain() Response
}
class Callback {
<<interface>>
+onResponse(Call call, Response response)
+onFailure(Call call, IOException error)
}
Runnable <|.. AsyncCall
Dispatcher --> "0..*" AsyncCall : 排队与限流
Dispatcher --> ExecutorService : 持有线程池
AsyncCall ..> ExecutorService : executeOn 提交自身
ExecutorService ..> Runnable : 执行 run
AsyncCall --> RealCall : inner class 的外层对象
AsyncCall --> Callback : responseCallback
RealCall ..> Dispatcher : 提交任务
enqueue() 创建 AsyncCall 后交给 Dispatcher。promoteAndExecute() 根据总量及每主机限制选择可运行任务,将其移到执行中队列,然后在锁外调用 executeOn(executorService)。run() 执行请求并交付结果,在 finally 中通知 Dispatcher 释放名额并尝试推进下一批任务。
4.9.3 默认异步限制为总量 64、每主机 5;这是**异步任务调度限制**,不是连接池大小,也不是同步请求总数上限。execute() 在调用线程直接执行,只向 Dispatcher 登记与移除运行记录,不会进入同一个异步等待队列。线程池拒绝任务时,也会报告失败并进行调度清理,不能假设所有失败回调都在正常工作线程中发生。
这里借用命令模式解释请求对象化、排队和执行解耦;源码没有提供 GoF 示例常见的 undo() 或命令历史。cancel() 是尽力中断未完成的调用,并不能撤销服务器已经处理的副作用。
源码:RealCall 的同步与异步入口、AsyncCall、Dispatcher。
装饰者:在流读写外增加计数和生命周期管理¶
Exchange 从协议编解码器取得原始 Sink 或 Source 后,会添加包装层。包装对象仍然提供同一种流接口,同时记录传输字节数、检查部分长度条件,并将完成或失败情况通知调用流程。
| 类或接口 | 模式角色 | 职责 |
|---|---|---|
okio.Sink、okio.Source |
组件接口 | 分别提供写入和读取字节的操作。 |
okio.ForwardingSink、okio.ForwardingSource |
转发装饰者基类 | 持有同接口的 delegate,默认把读写、关闭和超时操作转交给它。 |
Exchange.RequestBodySink |
请求侧具体装饰者 | 统计写入、检查长度、处理部分 I/O 异常并报告完成。 |
Exchange.ResponseBodySource |
响应侧具体装饰者 | 统计读取、发出开始与结束事件,报告读取或关闭失败。 |
Exchange |
包装层的创建者与收尾协作者 | 创建包装对象,接收 bodyComplete() 通知并调用 RealCall 的完成处理。 |
classDiagram
direction TB
class Sink["okio.Sink"] {
<<interface>>
+write(Buffer source, long byteCount)
+close()
}
class ForwardingSink["okio.ForwardingSink"] {
<<abstract>>
+Sink delegate
}
class RequestBodySink["Exchange.RequestBodySink"] {
+write(Buffer source, long byteCount)
+flush()
+close()
}
class Source["okio.Source"] {
<<interface>>
+read(Buffer sink, long byteCount) long
+close()
}
class ForwardingSource["okio.ForwardingSource"] {
<<abstract>>
+Source delegate
}
class ResponseBodySource["Exchange.ResponseBodySource"] {
+read(Buffer sink, long byteCount) long
+close()
}
Sink <|.. ForwardingSink
ForwardingSink <|-- RequestBodySink
ForwardingSink --> Sink : delegate
Source <|.. ForwardingSource
ForwardingSource <|-- ResponseBodySource
ForwardingSource --> Source : delegate
请求侧先将协议编解码器返回的 Sink 包成 RequestBodySink,再加缓冲并交给 RequestBody.writeTo();实际写入沿“RequestBody.writeTo() → 缓冲 Sink → RequestBodySink → 原始 Sink”调用。响应侧将原始 Source 包成 ResponseBodySource,再缓冲并交给 RealResponseBody。两个包装类都是 Exchange 的 inner 类,可以通过外层对象进行收尾;Exchange 创建它们,但没有专门的字段持续持有包装对象。
请求侧在成功写入后增加计数,关闭时检查已知长度等条件;响应侧在读取、EOF 或关闭等时机推进完成处理。complete() 用标志防止重复通知,Exchange.bodyComplete() 再更新请求/响应体事件并调用 RealCall.messageDone()。因此这不只是一个统计包装层,也参与调用资源的生命周期管理。
RequestBodySink 装饰的是 Sink,并不继承 RequestBody;ResponseBodySource 装饰的是 Source,并不继承 ResponseBody。部分异常检查在委托调用的捕获范围之外,响应包装器遇到 EOF 的分支也没有单独校验“是否已读满声明长度”,因此不应夸大为统一捕获所有错误或包办全部协议校验。
这两个转发基类来自 OkHttp 4.9.3 依赖的 Okio 2.8.0,不是 OkHttp 自己声明的类型。
源码:Exchange 的包装与完成处理、RealResponseBody、依赖版本配置、Okio ForwardingSink、Okio ForwardingSource。
原型:新建调用状态,复用原始请求描述¶
同一个 Call 的 execute() 和 enqueue() 都受执行标志保护,不能再次执行。clone() 则提供从现有调用创建新 Call 的统一契约,调用者不需要知道 RealCall 的构造方式。
| 类或接口 | 模式角色 | 职责 |
|---|---|---|
Call |
原型接口 | 声明 clone(): Call,提供可再次执行的新调用入口。 |
RealCall |
具体原型 | 通过构造器创建新的调用状态对象。 |
OkHttpClient |
共享配置与资源入口 | 克隆前后使用同一个客户端。 |
Request、RequestBody |
共享的请求描述与内容 | 克隆使用同一 originalRequest,因此也不会复制请求体的数据源或写入状态。 |
classDiagram
direction TB
class Call {
<<interface>>
+clone() Call
}
class RealCall {
+OkHttpClient client
+Request originalRequest
-AtomicBoolean executed
-boolean canceled
+clone() RealCall
}
class OkHttpClient
class Request {
+RequestBody body
}
class RequestBody {
<<abstract>>
+isOneShot() boolean
+isDuplex() boolean
}
Call <|.. RealCall
RealCall ..> RealCall : clone 创建新实例
RealCall --> OkHttpClient : 共享 client
RealCall --> Request : 共享 originalRequest
Request --> "0..1" RequestBody : body
RealCall.clone() 实际返回 RealCall(client, originalRequest, forWebSocket),并未调用 super.clone()。新的对象重新初始化执行标志、取消状态、超时和交换状态,也会重新调用事件监听器工厂;已有网络交换不会被复制。客户端与原始请求则继续共享。
新 Call 可以被执行,不代表原请求体一定可以重放。 RequestBody.isOneShot() 表示内容至多接受一次 writeTo(),clone 不会因此拒绝创建,也不会重新打开已经消耗的数据源。自动失败恢复会结合请求是否已经开始发送及一次性标志决定能否重试;手动克隆同样需要调用者确认内容可再次提供。
isDuplex() 表示请求和响应可以交错读写,与 isOneShot() 是两个独立标志。该版本双工传输要求 HTTP/2;不能简单写成“所有双工请求都禁止克隆或重试”。共享同一个请求体时,它的异步写入状态和并发使用能力也没有被复制或重置。
前面的 newBuilder() 则用于派生配置或请求描述:虽然同样从已有实例出发,它返回的是 Builder,仍需继续构建产品。两类 API 都有共享边界,不能把它们统称为深克隆整个请求运行环境。
源码:Call.clone() 契约、RealCall.clone()、RequestBody 的一次性和双工说明、重试与后续请求判断、HTTP/1 的双工限制。
观察者式事件通知:记录一次调用的生命周期¶
网络监控通常关心 DNS、连接、TLS、请求体和响应体等阶段。如果直接把统计逻辑写进每个阶段,就会让网络实现与具体监控系统耦合。EventListener 用统一事件接口接收这些通知,应用可以选择关心的事件进行记录。
| 类或接口 | 对应角色 | 职责 |
|---|---|---|
EventListener |
事件观察接口的抽象类 | 提供 callStart()、dnsStart()、responseBodyEnd() 等默认空实现方法,子类按需覆写。 |
EventListener.Factory |
监听器创建接口 | 在创建 Call 时提供其生命周期使用的监听器。 |
OkHttpClient |
监听配置持有者 | 保存 eventListenerFactory,由 Builder 配置。 |
RealCall |
监听器作用域的持有者 | 构造时调用工厂并保存一个监听器引用,发送调用开始、结束和取消等事件。 |
Exchange、RouteSelector |
事件产生者 | 接收同一 Call 的监听器引用,分别通知传输和 DNS 等阶段事件。 |
classDiagram
direction TB
class OkHttpClient {
+EventListener.Factory eventListenerFactory
}
class ListenerFactory["EventListener.Factory"] {
<<interface>>
+create(Call call) EventListener
}
class RealCall {
~EventListener eventListener
}
class EventListener {
<<abstract>>
+callStart(Call call)
+dnsStart(Call call, String domainName)
+responseBodyEnd(Call call, long byteCount)
+callEnd(Call call)
}
class Exchange {
~EventListener eventListener
}
class RouteSelector {
-EventListener eventListener
}
OkHttpClient --> ListenerFactory : eventListenerFactory
RealCall ..> ListenerFactory : 构造时 create
ListenerFactory ..> EventListener : 返回
RealCall --> EventListener : 保存并通知
Exchange --> EventListener : 使用同一调用的监听器
RouteSelector --> EventListener : 使用同一调用的监听器
创建发生在 RealCall 的属性初始化阶段:client.eventListenerFactory.create(this),不是第一次执行时,也不是每次重试时。后续创建 Exchange、ExchangeFinder 和 RouteSelector 时,会把同一 Call 的监听器向下传递。一项 Call 内的重试可能重复产生部分阶段事件,但仍然属于这个 Call 的监听范围。
“每个 Call 调用一次工厂”不保证“每个 Call 都得到一个新监听器实例”。Builder.eventListener(listener) 会把已有实例包装成始终返回它的工厂;默认的 EventListener.NONE 也是共享的空实现。若要记录每个调用独立的可变数据,应由工厂提供合适的实例作用域。
它具有观察者式的通知解耦,但核心实现是**每 Call 一个监听器引用**,没有 addObserver/removeObserver、动态订阅列表或遍历广播。EventListener.Factory 也不继承 EventListener。图中关联表示使用关系,不表示监听器必须被某个 Call 独占。
事件方法在触发事件的线程上调用,没有独立统一的通知线程;取消可能与其他事件并发。它和 Callback 的用途也不同:EventListener 用于过程监控,Callback 用于交付异步结果。
源码:EventListener 及其 Factory、RealCall 的监听器初始化、Builder 的监听配置、asFactory()、ExchangeFinder 的传递。
连接池与旧摘录的阅读边界¶
连接复用是这里另一个重要设计,但更适合按**连接资源池**理解。RealConnectionPool 管理 RealConnection,获取时检查连接是否允许复用、地址与路由是否匹配、是否要求多路复用;清理任务根据空闲时长和空闲数量淘汰连接。连接包含 socket、协议与使用状态,不能只因为“多个调用共享”就直接认定为 GoF 享元。
需要区分四种对象:Request 描述要做什么,Call 保存本次调用状态,Exchange 表示一次网络交换,RealConnection 表示可复用的连接。一项 Call 可能因重试或重定向经历多项 Exchange;多个 Call 也可能先后复用连接,HTTP/2 下还可以在符合条件时共享同一条连接上的不同流。
默认的“5 个、5 分钟”指连接池的**最大空闲连接数与空闲保活时长**,不表示最多只能创建 5 个连接。连接池的自动空闲清理由 TaskRunner 的任务队列安排;不能把所有清理描述成调用 put() 的线程立即循环关闭连接。异步并发限制则由前面的 Dispatcher 管理。
| 容易按名称或表面结构误判的地方 | 对照源码应如何理解 |
|---|---|
| 建议全局复用 OkHttpClient | 是资源复用建议。客户端允许创建多个实例,不是强制单例类。 |
BridgeInterceptor |
负责用户请求与 HTTP 传输细节的衔接,如补请求头、Cookie、透明 gzip;名称不足以证明 GoF 桥接结构。 |
CacheStrategy |
保存 networkRequest、cacheResponse 等缓存决策结果。不能仅凭 Strategy 后缀认为它自身就是可替换算法接口;前文用 Dns 展示更明确的策略结构。 |
| 所有拦截器都要继续链 | 缓存命中或应用层合成响应可以短路;网络拦截器才有特定的恰好一次约束。 |
| Callback、EventListener 都是回调 | 前者交付异步请求结果,后者观察生命周期;它们不是同一个订阅机制。 |
| Java 片段与 Kotlin 片段混合 | 旧摘录中的字段、构造方式未必属于同一版本;新增 UML 以本节固定的 4.9.3 源码为准。 |
源码:ConnectionPool、RealConnectionPool、BridgeInterceptor、CacheStrategy。






