Android开发[14]:网络优化之OkHttp

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()只调用一次,重复读取为空
相关推荐
千里马学框架5 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台5 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone5 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc5 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo5 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077005 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
ai2work5 天前
ch23 综合复刻:从零做一个最小可用版本(capstone)
kotlin
其实防守也摸鱼5 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
ai2work5 天前
ch21 签名、校验与发版
kotlin
AFinalStone5 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui