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()只调用一次,重复读取为空
相关推荐
私人珍藏库1 小时前
[Android] FX Player-安卓全格式播放器-比MX播放器好用
android·学习·工具·软件·多功能
写点啥呢2 小时前
车机 Android 开机优化复盘:我怎么和 AI 一起把问题定位到 SystemUI
android·人工智能
Peter(阿斯拉)3 小时前
[Android]_[中级]_[如何创建MVVM架构原型]
android·java·架构·mvvm·viewmodel
kingbal3 小时前
Flutter:Flutter SDK版本管理工具FVM
android·flutter·ios·android-studio·window
天天开发3 小时前
Flutter状态管理新宠:RiverPod全面解析与实战指南
android·flutter
ltlovezh19 小时前
ROI 编码学习指南:Android 与 FFmpeg 的真实实现边界
android·ffmpeg·音视频开发
心前阳光20 小时前
Unity之2021.3.45f2c1发布安卓程序遇到的问题
android·unity·游戏引擎
utf8mb4安全女神21 小时前
MySQL5.7升级到MySQL8.0并进行数据迁移
android
黄林晴21 小时前
Android XR DP4 重磅发布:手机 App 直投眼镜,Compose 原生玩转 3D 内容
android·google io