Android网络优化之OkHttp
今日目标
- 掌握OkHttp整体四层架构,厘清请求全链路执行顺序与核心组件职责。
- 吃透责任链设计模式底层,搞懂五大系统拦截器各司其职、递归执行原理。
- 精通Dispatcher调度器原理,掌握同步/异步请求、最大并发、队列调度机制。
- 深度理解ConnectionPool连接池复用核心,弄懂TCP长连接复用、空闲清理、HTTP2多路复用。
- 掌握OkHttp缓存策略、重试重定向、请求头自动补全、协议编解码底层逻辑。
理论
OkHttp四层整体架构
OkHttp采用分层解耦架构,从上至下职责清晰,所有网络请求严格遵循该链路执行。
- 应用层:OkHttpClient、Request、Response,对外暴露API,开发者仅需操作这一层。
- 调度层:Dispatcher调度器,统一管理同步/异步请求、线程池、并发队列、请求限流。
- 拦截器层(核心层):自定义拦截器+五大系统拦截器组成责任链,逐层处理请求响应。
- 连接层:ConnectionPool连接池、RealConnection连接、HttpCodec协议编解码,负责TCP连接建立、复用、数据读写。
完整请求链路
- 构造Request → OkHttpClient.newCall() → Dispatcher调度 → 拦截器责任链逐层执行 → 连接池获取连接 → TCP数据读写 → 响应逐层回调返回。
核心入口源码流程(从调用到网络请求)
所有OkHttp请求,最终都会进入核心方法getResponseWithInterceptorChain(),这是源码总入口。
执行步骤
- 1.创建OkHttpClient(全局单例,禁止重复创建)。
- 2.构建Request请求参数(Url、Header、Method、Body)。
- 3.调用newCall()生成RealCall请求任务。
- 4.同步execute()直接执行,异步enqueue()入Dispatcher队列。
- 5.最终进入getResponseWithInterceptorChain(),组装拦截器责任链并递归执行。
- 6.完成网络请求、数据解析、响应回调。
五大系统拦截器精讲
OkHttp核心灵魂:责任链模式。
- 将复杂网络逻辑拆分为5个独立拦截器,单向递归执行,请求自上而下、响应自下而上,完全解耦。
- 拦截器执行顺序:重试拦截器→桥接拦截器→缓存拦截器→连接拦截器→服务调用拦截器。
1.RetryAndFollowUpInterceptor 重试重定向拦截器(第一层)
- 职责:全局故障兜底,负责请求失败重试、HTTP重定向、异常恢复。
- 逻辑
- 捕获下层所有网络异常、超时、连接失败,根据配置判断是否重试。
- 处理301/302/307重定向逻辑,限制最大重定向次数,防止死循环。
- 价值:提升网络稳定性,自动适配弱网、链路切换、服务器地址变更场景。
2.BridgeInterceptor 桥接拦截器(第二层)
- 职责:自动补全HTTP请求头、处理Cookie、编码转换,抹平HTTP协议差异。
- 逻辑
- 自动添加Content-Type、Host、Connection、User-Agent等必备请求头。
- 自动读取、保存、携带Cookie。
- 统一请求体编码、响应体解码。
- 价值:简化开发者使用,无需手动适配HTTP协议细节,规范请求格式。
3.CacheInterceptor 缓存拦截器(第三层)
- 职责:实现HTTP标准缓存策略,按需读取本地缓存、写入新缓存、失效淘汰。
- 逻辑
- 根据Cache-Control、Expires、ETag、Last-Modified判断缓存是否有效。
- 有效则直接返回本地缓存,不请求网络。
- 失效则走网络请求并更新缓存。
- 价值:减少网络请求、节省流量、提升页面加载速度、降低服务器压力。
4.ConnectInterceptor 连接拦截器(第四层)
- 职责:从连接池获取可用TCP连接,建立通道,绑定请求与连接。
- 逻辑
- 根据请求Address匹配连接池空闲连接,优先复用。
- 无空闲连接则新建TCP连接,完成三次握手。
- 将连接绑定到当前请求,交给下层执行IO读写。
- 价值:实现TCP长连接复用,避免频繁三次握手、四次挥手,大幅降低请求耗时。
5.CallServerInterceptor 服务调用拦截器(最后一层)
- 职责:真正执行网络IO,唯一做读写操作的拦截器。
- 逻辑
- 通过HttpCodec编解码工具,向服务端写入请求头、请求体。
- 读取服务端响应头、响应体。
- 组装Response对象逐层返回。
- 价值:所有前置拦截器都是预处理,只有该拦截器执行真实网络通信。
责任链递归执行原理
所有拦截器统一实现Interceptor接口,通过chain.proceed()实现递归串联。
- 每个拦截器先处理请求前置逻辑。
- 调用proceed()将请求传递给下一个拦截器。
- 直到最后一层服务拦截器完成网络IO。
- 响应结果逐层向上回调,每个拦截器处理响应后置逻辑。
- 最终返回完整Response。
核心优势:功能完全解耦、层级清晰、支持自定义拦截器插入扩展,符合开闭原则。
Dispatcher调度器原理(请求并发管控)
Dispatcher是OkHttp的请求调度中枢,负责线程池管理、并发控制、队列调度,彻底避免请求拥堵、线程泛滥。
核心配置(默认)
- 最大异步并发数:6(同一时间最多6个异步请求同时执行)。
- 同步请求:无队列,直接执行。
- 空闲线程超时:60秒,自动回收闲置线程,节省内存。
双队列机制
- 正在执行队列:存放当前运行中的异步请求。
- 等待队列:超过最大并发数的请求进入等待排队,空闲后自动补位执行。
ConnectionPool连接池复用核心
TCP短连接每次请求都需要三次握手、四次挥手,耗时极高,OkHttp核心优势就是长连接复用。
核心机制
- 默认配置:最大空闲连接5个,空闲连接存活时长5分钟。
- 复用规则:通过Address(主机、端口、协议、SSL信息)匹配空闲连接,相同域名请求优先复用已有TCP连接。
- 自动清理:后台线程定时扫描,清理超时空闲连接,释放内存与端口资源。
- HTTP/2多路复用:一个TCP连接可同时承载多个HTTP请求,极大提升并发效率。
核心价值:规避频繁创建销毁TCP连接的开销,大幅提升网络请求速度,降低延迟。
源码
同步请求execute()
- 阻塞当前线程,直到请求完成 / 失败才往下走。
- 不能在主线程调用。
- 直接返回Response,有异常代码直接抛IOException,需要try-catch。
- call.cancel()可中断阻塞。
发起请求
- 直接返回Response
kotlin
suspend fun get(url: String): Response = withContext(Dispatchers.IO) {
val request = Request.Builder().url(url).get().build()
okHttpClient.newCall(request).execute()
}
execute源码
- 直接执行getResponseWithInterceptorChain()
kotlin
override fun execute(): Response {
check(executed.compareAndSet(false, true)) { "Already Executed" }
timeout.enter()
callStart()
try {
client.dispatcher.executed(this)
return getResponseWithInterceptorChain()
} finally {
client.dispatcher.finished(this)
}
}
异步请求enqueue()
- 不阻塞当前线程,内部开子线程执行,结果通过回调返回。
- 主线程/子线程都能直接调用。
- 无返回值,结果在onResponse/onFailure回调,有异常进入onFailure,不会向外抛。
- call.cancel() 终止任务。
发起请求
- 回调获取结果
kotlin
suspend fun get1(url: String) {
withContext(Dispatchers.IO) {
val request = Request.Builder().url(url).get().build()
okHttpClient.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
}
override fun onFailure(call: Call, e: IOException) {
}
})
}
}
enqueue源码
- Dispatcher#enqueue()入Dispatcher队列
- 线程池中执行getResponseWithInterceptorChain()
kotlin
override fun enqueue(responseCallback: Callback) {
check(executed.compareAndSet(false, true)) { "Already Executed" }
callStart()
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
// Dispatcher#enqueue()
internal fun enqueue(call: AsyncCall) {
promoteAndExecute(enqueuedCall = call)
}
private fun promoteAndExecute(
enqueuedCall: AsyncCall? = null,
finishedCall: RealCall? = null,
finishedAsyncCall: AsyncCall? = null,
) {
// 前置校验&状态标记
assertLockNotHeld() // 断言当前没有持有对象锁,防止嵌套死锁,是 OkHttp 锁规范。
val executorIsShutdown = executorService.isShutdown // 标记线程池是否已关闭(应用退出 / 销毁调度器时触发)。
// Actions to take outside the synchronized block.
class Effects(
val callsToExecute: List<AsyncCall>, // 本轮要丢给线程池执行的请求
val idleCallbackToRun: Runnable?, // 空闲回调(全部请求跑完后执行)
)
// 整个集合状态读写全部加锁,保证多线程并发安全。
val effects =
synchronized(this) {
if (finishedCall != null) {
// 处理「已完成的同步请求」
check(runningSyncCalls.remove(finishedCall)) { "Call wasn't in-flight!" }
}
if (finishedAsyncCall != null) {
// 处理「已完成的异步请求」
finishedAsyncCall.callsPerHost.decrementAndGet()
check(runningAsyncCalls.remove(finishedAsyncCall)) { "Call wasn't in-flight!" }
}
if (enqueuedCall != null) {
// 处理「新入队的异步请求」
readyAsyncCalls.add(enqueuedCall) // 新异步请求先加入等待队列 readyAsyncCalls。
// Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
// the same host.
if (!enqueuedCall.call.forWebSocket) {
val existingCall = findExistingCallWithHost(enqueuedCall.host)
if (existingCall != null) enqueuedCall.reuseCallsPerHostFrom(existingCall)
}
}
val becameIdle =
(finishedCall != null || finishedAsyncCall != null) &&
(executorIsShutdown || runningAsyncCalls.isEmpty()) &&
runningSyncCalls.isEmpty()
val idleCallbackToRun = if (becameIdle) idleCallback else null
if (executorIsShutdown) {
// 线程池已关闭:把所有排队请求全部取出,后续统一执行「拒绝策略」;清空等待队列。
return@synchronized Effects(
callsToExecute = readyAsyncCalls.toList().also { readyAsyncCalls.clear() },
idleCallbackToRun = idleCallbackToRun,
)
}
// 线程池正常,从等待队列取出可执行请求(限流核心)
val callsToExecute = mutableListOf<AsyncCall>()
val i = readyAsyncCalls.iterator()
while (i.hasNext()) {
val asyncCall = i.next()
if (runningAsyncCalls.size >= this.maxRequests) break // 1. 全局最大并发限制:runningAsyncCalls 超过上限,停止取任务
if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // 2. 单域名最大并发限制:当前域名请求数超限,跳过该任务(继续遍历下一个)
i.remove() // 符合条件:移出等待队列
asyncCall.callsPerHost.incrementAndGet() // 域名计数器 +1
// 加入待执行列表、运行中列表
callsToExecute.add(asyncCall)
runningAsyncCalls.add(asyncCall)
}
return@synchronized Effects(
callsToExecute = callsToExecute,
idleCallbackToRun = idleCallbackToRun,
)
}
var callDispatcherQueueStart = true
for (i in 0 until effects.callsToExecute.size) {
val call = effects.callsToExecute[i]
// If the newly-enqueued call is already out, skip its dispatcher queue events. We only
// publish those events for calls that have to wait.
if (call === enqueuedCall) {
callDispatcherQueueStart = false
} else {
call.call.eventListener.dispatcherQueueEnd(call.call, this)
}
if (executorIsShutdown) {
// 线程池关闭,拒绝请求
call.failRejected()
} else {
// 丢给线程池真正执行
call.executeOn(executorService)
}
}
if (callDispatcherQueueStart && enqueuedCall != null) {
enqueuedCall.call.eventListener.dispatcherQueueStart(enqueuedCall.call, this)
}
effects.idleCallbackToRun?.run()
}
最终执行源码getResponseWithInterceptorChain()
组装拦截器责任链并递归执行。
kotlin
@Throws(IOException::class)
internal fun getResponseWithInterceptorChain(): Response {
// 组装拦截器
val interceptors = mutableListOf<Interceptor>()
interceptors += client.interceptors
interceptors += RetryAndFollowUpInterceptor(client)
interceptors += BridgeInterceptor(client.cookieJar)
interceptors += CacheInterceptor(client.cache)
interceptors += ConnectInterceptor
if (!forWebSocket) {
interceptors += client.networkInterceptors
}
interceptors += CallServerInterceptor
val chain =
RealInterceptorChain(
call = this,
interceptors = interceptors,
index = 0,
exchange = null,
request = originalRequest,
connectTimeoutMillis = client.connectTimeoutMillis,
readTimeoutMillis = client.readTimeoutMillis,
writeTimeoutMillis = client.writeTimeoutMillis,
)
var calledNoMoreExchanges = false
try {
// 递归执行
val response = chain.proceed(originalRequest)
if (isCanceled()) {
response.closeQuietly()
throw IOException("Canceled")
}
return response
} catch (e: IOException) {
calledNoMoreExchanges = true
throw noMoreExchanges(e) as Throwable
} finally {
if (!calledNoMoreExchanges) {
noMoreExchanges(null)
}
}
}
小结
OkHttp的核心架构是什么?完整请求流程
OkHttp采用四层分层架构,从上到下分为应用层、调度层、拦截器层、连接层。
- 完整请求流程:首先通过OkHttpClient构建Request请求,生成RealCall任务。
- 同步请求直接执行,异步请求进入Dispatcher调度器,通过线程池和双队列管控并发。
- 随后进入核心的拦截器责任链,依次经过重试重定向、桥接、缓存、连接、服务调用五大系统拦截器,逐层处理请求预处理。
- 通过连接池匹配复用TCP长连接,最后执行网络IO读写获取响应,响应结果逐层回调返回,完成一次完整网络请求。
OkHttp五大拦截器各自作用是什么?执行顺序?
执行顺序固定:重试拦截器→桥接拦截器→缓存拦截器→连接拦截器→服务调用拦截器。
- 重试拦截器:处理网络异常重试、HTTP重定向,保障请求稳定性。
- 桥接拦截器:自动补全请求头、处理Cookie、编解码转换,适配HTTP协议。
- 缓存拦截器:实现HTTP缓存策略,优先读取本地缓存,减少网络请求。
- 连接拦截器:从连接池获取/新建TCP连接,实现长连接复用。
- 服务拦截器:唯一执行网络IO读写,完成请求发送与响应接收。
OkHttp责任链模式原理,为什么要用这种设计?
- OkHttp通过责任链模式将复杂网络逻辑拆分为多个独立拦截器,每个拦截器只负责单一职责。
- 通过chain.proceed()实现递归传递,请求自上而下逐层预处理,响应自下而上逐层后置处理。
- 该设计彻底解耦网络重试、缓存、连接、协议处理等核心能力,扩展性极强,支持自定义拦截器插入扩展,符合单一职责与开闭原则,便于迭代维护与功能扩展。
OkHttp连接池复用原理?默认参数是什么?
OkHttp连接池核心是TCP长连接复用,避免频繁三次握手、四次挥手损耗。
- 通过Address(域名、端口、协议、SSL)匹配空闲连接,相同域名请求优先复用已有TCP连接。
- 默认配置最大空闲连接5个,空闲连接存活5分钟,后台线程自动清理超时闲置连接。
- 同时支持HTTP/2多路复用,单TCP连接可承载多请求并发,大幅提升网络请求效率、降低延迟。
使用OkHttp有哪些常见坑?如何规避?
- 频繁创建OkHttpClient
- 坑点:导致连接池失效、线程泛滥
- 修复:全局单例复用
- ResponseBody未关闭
- 坑点:引发连接泄漏、内存泄漏
- 修复:必须主动close或使用自动关闭API
- 自动重试导致重复提交
- 修复:POST请求禁止自动重试,做接口幂等防护
- 缓存策略不当出现脏数据
- 修复:精准配置Cache-Control,区分缓存场景、手动失效更新
- 异步并发拥堵
- 修复:合理调整Dispatcher最大并发数,及时取消无效请求,释放资源
- Response只能读取一次
- 修复:无论同步/异步,response.body.string()/bytes()只调用一次,重复读取为空