4. 2026金三银四 Android OkHttp 面试核心 45 问:从源码到架构深度解析

28 阅读22分钟

Q1:请解释OkHttp中的责任链设计模式是如何实现的?拦截器链的整体执行流程是怎样的?

答案

OkHttp使用责任链模式(Chain of Responsibility)将网络请求的复杂处理过程拆解为多个独立的拦截器,每个拦截器负责特定功能,按顺序串联执行。核心入口在RealCall.getResponseWithInterceptorChain()方法中。

核心源码(OkHttp 4.x / Kotlin版本):

// RealCall.kt
internal fun getResponseWithInterceptorChain(): Response {
    // 构建拦截器列表
    val interceptors = mutableListOf<Interceptor>()
    interceptors += client.interceptors  // 1. 应用拦截器(自定义)
    interceptors += RetryAndFollowUpInterceptor(client)  // 2. 重试重定向拦截器
    interceptors += BridgeInterceptor(client.cookieJar)  // 3. 桥接拦截器
    interceptors += CacheInterceptor(client.cache)       // 4. 缓存拦截器
    interceptors += ConnectInterceptor(client)           // 5. 连接拦截器
    if (!forWebSocket) {
        interceptors += client.networkInterceptors       // 6. 网络拦截器(自定义)
    }
    interceptors += CallServerInterceptor(forWebSocket)  // 7. 请求服务拦截器

    // 创建责任链,递归执行
    val chain = RealInterceptorChain(
        interceptors = interceptors,
        index = 0,
        request = originalRequest,
        call = this,
        ...
    )
    return chain.proceed(originalRequest)  // 启动链式调用
}

流程图

┌─────────────────────────────────────────────────────────────────────────────┐
│                         RealCall.getResponseWithInterceptorChain()          │
└─────────────────────────────────────────────────────────────────────────────┘
                                          │
                                          ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│  拦截器列表构建完成,创建RealInterceptorChain(index=0),调用proceed(request)   │
└─────────────────────────────────────────────────────────────────────────────┘
                                          │
                                          ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                          责任链递归执行(先进后出)                            │
│                                                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ 【请求方向 →】                                                        │   │
│  │                                                                      │   │
│  │  Interceptor 0 ──→ Interceptor 1 ──→ Interceptor 2 ──→ ...          │   │
│  │   (自定义应用拦截器)    (RetryAndFollowUp)   (Bridge)                  │   │
│  │        │                     │                 │                      │   │
│  │        ▼                     ▼                 ▼                      │   │
│  │  前置处理               前置处理           前置处理                    │   │
│  │        │                     │                 │                      │   │
│  │   chain.proceed() →   chain.proceed() →  chain.proceed() →           │   │
│  │        │                     │                 │                      │   │
│  │  后置处理               后置处理           后置处理                    │   │
│  │                                                                      │   │
│  │ 【← 响应方向】                                                        │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│  ... → Interceptor 4 ──→ Interceptor 5 ──→ Interceptor 6                   │
│        (Cache)            (Connect)          (CallServer)                   │
│           │                   │                   │                         │
│           ▼                   ▼                   ▼                         │
│       前置处理             前置处理            执行真正的网络请求            │
│           │                   │              (Socket I/O)                   │
│   chain.proceed() →   chain.proceed() →           │                         │
│           │                   │                   ▼                         │
│       后置处理             后置处理            返回原始Response              │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

RealInterceptorChain.proceed() 核心逻辑

// RealInterceptorChain.kt
fun proceed(request: Request): Response {
    // 检查当前索引是否越界
    if (index >= interceptors.size) throw AssertionError()
    
    // 关键:每次递归调用创建新的Chain实例,index + 1
    val next = RealInterceptorChain(
        interceptors = interceptors,
        index = index + 1,
        request = request,
        call = call,
        ...
    )
    
    // 获取当前拦截器,调用其intercept方法
    val interceptor = interceptors[index]
    val response = interceptor.intercept(next)
    
    return response
}

执行特点

  1. 请求阶段:拦截器按列表顺序从前向后执行前置处理
  2. 响应阶段:拦截器按相反顺序(后进先出)从后向前执行后置处理
  3. 递归机制:每个拦截器调用chain.proceed()将控制权交给下一个拦截器,等待其返回Response后再进行后置处理

Q2:OkHttp中有哪些内置拦截器?各自的作用是什么?

答案

顺序拦截器核心作用关键源码类
0应用拦截器业务层全局处理(日志、公共参数、认证)自定义Interceptor
1RetryAndFollowUpInterceptor失败重试、重定向处理(3xx)、最大20次RetryAndFollowUpInterceptor.kt
2BridgeInterceptor请求/响应转换:添加标准Header(Content-Type、Host、Accept-Encoding、Cookie),Gzip解压BridgeInterceptor.kt
3CacheInterceptorHTTP缓存策略:强缓存/协商缓存判断,缓存读写CacheInterceptor.kt
4ConnectInterceptor连接池管理:获取/创建连接,Socket建立ConnectInterceptor.kt
5网络拦截器网络层全局处理(可获取真正的网络请求/响应)自定义Interceptor
6CallServerInterceptor实际I/O操作:向Socket写入请求,读取响应CallServerInterceptor.kt

各拦截器详细说明

  • RetryAndFollowUpInterceptor:处理RouteException或IOException后判断是否可恢复,通过followUpRequest()处理3xx重定向。
  • BridgeInterceptor:添加Content-Type、Content-Length、Host、Connection、Accept-Encoding:gzip等Header,Response阶段做Gzip解压。
  • CacheInterceptor:通过CacheStrategy判断缓存可用性,实现强缓存(直接返回)和协商缓存(带If-Modified-Since)。
  • ConnectInterceptor:调用StreamAllocation.newStream()从连接池获取RealConnection。
  • CallServerInterceptor:写入请求头/请求体,读取响应头/响应体,是最后一个拦截器。

Q3:缓存拦截器(CacheInterceptor)的具体工作原理是什么?如何配置OkHttp缓存?

答案

核心流程

  1. 读取候选缓存:根据请求从Cache中获取缓存的Response
  2. 创建缓存策略:通过CacheStrategy.Factory结合请求头(Cache-Control)和缓存的响应头,计算策略
  3. 策略判断
    • 强缓存命中 → networkRequest == null,直接返回cacheResponse
    • 协商缓存 → 构造带If-Modified-Since/If-None-Match的networkRequest
    • 无缓存 → 正常发起网络请求
  4. 缓存响应:网络请求返回后,将新Response写入缓存

源码片段(简化版):

// CacheInterceptor.kt
override fun intercept(chain: Interceptor.Chain): Response {
    // 1. 尝试从缓存获取
    val cacheResponse = cache?.get(chain.request())
    
    // 2. 计算缓存策略
    val strategy = CacheStrategy.Factory(chain.request(), cacheResponse).compute()
    val networkRequest = strategy.networkRequest
    val cacheResponse = strategy.cacheResponse
    
    // 3. 根据策略决定是否发起网络请求
    if (networkRequest == null && cacheResponse == null) {
        return Response.Builder().code(504).message("Unsatisfiable Request.").build()
    }
    if (networkRequest == null) {
        return cacheResponse!!.newBuilder().cacheResponse(stripBody(cacheResponse)).build()
    }
    
    // 4. 发起网络请求
    val networkResponse = chain.proceed(networkRequest)
    
    // 5. 更新缓存
    if (cacheResponse != null) {
        if (networkResponse?.code() == 304) {  // Not Modified
            return cacheResponse.newBuilder()
                .headers(networkResponse.headers)
                .cacheResponse(stripBody(cacheResponse))
                .networkResponse(stripBody(networkResponse))
                .build()
        }
    }
    if (networkResponse != null) {
        cache?.put(networkResponse)
    }
    return networkResponse
}

配置缓存

val cacheSize = 50 * 1024 * 1024 // 50 MiB
val cache = Cache(cacheDir, cacheSize)

val client = OkHttpClient.Builder()
    .cache(cache)
    .build()

OkHttp默认不支持缓存,需显式设置Cache对象。

Q4:OkHttp如何复用TCP连接?连接池(ConnectionPool)复用的依据是什么?如何清除空闲连接?

答案

复用依据

连接池ConnectionPool内部使用ArrayDeque<RealConnection>存储连接。获取连接时遍历池中连接,通过RealConnection.isEligible()判断是否匹配,判断条件包括:

  1. 地址匹配:host、port、proxy一致
  2. 协议兼容:HTTP/1.1需支持Keep-Alive,HTTP/2自动支持多路复用
  3. SSL配置匹配:证书和hostnameVerifier一致
  4. 连接可用:未被关闭或回收

复用核心代码(简化):

// ConnectionPool.kt
internal fun get(address: Address, ...): RealConnection? {
    for (connection in connections) {
        if (connection.isEligible(address, ...)) {
            connection.acquire()  // 增加引用计数
            return connection
        }
    }
    return null
}

引用计数机制

  • StreamAllocation对象管理每个连接的引用计数
  • 每次请求获得连接时acquire(),释放时release()
  • streamAllocationCount == 0时连接变为空闲,可被清理

空闲连接清除机制

// ConnectionPool.kt
private val cleanupRunnable = Runnable {
    while (true) {
        val waitNanos = cleanup(System.nanoTime())  // 执行清理
        if (waitNanos == -1L) return
        if (waitNanos > 0L) {
            val waitMillis = waitNanos / 1000000L
            synchronized(this) { (this as Object).wait(waitMillis) }
        }
    }
}

internal fun cleanup(now: Long): Long {
    var inUseConnectionCount = 0
    var idleConnectionCount = 0
    var longestIdleConnection: RealConnection? = null
    var longestIdleDurationNs = 0L
    
    for (connection in connections) {
        if (connection.isInUse()) {
            inUseConnectionCount++
        } else {
            idleConnectionCount++
            val idleDurationNs = now - connection.idleAtNs
            if (idleDurationNs > longestIdleDurationNs) {
                longestIdleDurationNs = idleDurationNs
                longestIdleConnection = connection
            }
        }
    }
    
    // 清理条件:空闲连接数 > maxIdleConnections(5) OR 最长空闲时间 > keepAliveDurationNs(5分钟)
    if (longestIdleDurationNs >= keepAliveDurationNs || 
        idleConnectionCount > maxIdleConnections) {
        connections.remove(longestIdleConnection)
        return 0L
    }
    return keepAliveDurationNs - longestIdleDurationNs  // 下次清理的等待时间
}

清理规则(默认值):

  • 最大空闲连接数:5个
  • 空闲连接存活时间:5分钟
  • 超过任一条件即被清理

Q5:一个TCP连接可以同时发送多个HTTP请求吗?OkHttp是如何支持的?

答案

取决于HTTP协议版本:

HTTP/1.1不支持单连接并发。一个连接同时只能处理一个请求(队头阻塞),需等待响应返回后才能发送下一个。但OkHttp连接池会复用Keep-Alive连接串行处理后续请求。

HTTP/2支持多路复用。单个TCP连接可同时并发处理多个请求(Stream),每个Stream有独立ID,请求和响应以二进制帧交错传输,解决队头阻塞问题。

HTTP/2多路复用原理

┌─────────────────────────────────────────────────────────────┐
│                      单个 TCP 连接                           │
├─────────────────────────────────────────────────────────────┤
│  Stream 1 Request  │  Stream 2 Request  │  Stream 3 Request  │
│  (帧1)             │  (帧1)             │  (帧1)             │
├─────────────────────────────────────────────────────────────┤
│  Stream 1 Response │  Stream 3 Request  │  Stream 2 Response │
│  (帧2)             │  (帧2)             │  (帧2)             │
├─────────────────────────────────────────────────────────────┤
│  Stream 2 Request  │  Stream 1 Response │  Stream 3 Response │
│  (帧3)             │  (帧3)             │  (帧2)             │
└─────────────────────────────────────────────────────────────┘
        ↑                    ↑                    ↑
    交错发送/接收,接收端根据Stream ID重组完整的请求/响应

OkHttp对HTTP/2开箱即用,前提是服务端支持HTTP/2且客户端未禁用。一个RealConnection在HTTP/2模式下可关联多个Http2Stream,每个Stream对应一个Call。

Q6:如何取消一个正在进行的网络请求?取消后的底层发生了什么?

答案

取消方式

// 方式1:单个请求取消
val call = okHttpClient.newCall(request)
call.enqueue(callback)
// 需要取消时
call.cancel()

// 方式2:按Tag批量取消
val request = Request.Builder()
    .url(url)
    .tag("user_avatar")  // 设置tag
    .build()
// 批量取消相同tag的请求
okHttpClient.dispatcher().cancelAllWithTag("user_avatar")

取消原理

  1. 设置取消标志Call.cancel()RealCall中设置canceled标志为true
  2. 中断Socket I/O:取消正在进行的Exchange(对应底层Socket流)
  3. 清理Dispatcher队列:如果请求在队列中未开始,直接从等待队列移除
  4. 触发回调:异步回调的onFailure()收到IOException("Canceled")

源码关键路径

// RealCall.kt
override fun cancel() {
    canceled = true
    // 如果已有exchange(正在读写),中断IO
    exchange?.cancel()
}

// Exchange.kt
fun cancel() {
    // 关闭底层的Socket
    call?.cancel()
}

注意事项

  • 取消不保证立即停止,若请求已在传输中,可能稍后抛出SocketException
  • 取消后可通过call.isCanceled()检查状态

Q7:能否在自定义拦截器中直接返回Response而不调用chain.proceed()?什么场景下需要这样做?

答案

可以。 拦截器的核心能力之一就是“短路”(short-circuit)责任链。

实现方式

class MockInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        
        // 条件判断:特定URL或特定条件满足时,返回Mock数据
        if (request.url.toString().contains("/test")) {
            return Response.Builder()
                .request(request)
                .protocol(Protocol.HTTP_1_1)
                .code(200)
                .message("OK")
                .body("Mock response body".toResponseBody())
                .build()
        }
        
        // 其他情况正常走网络
        return chain.proceed(request)
    }
}

典型使用场景

场景说明
离线模式/Mock数据开发调试阶段,无网络时返回预设数据
条件请求阻断根据业务条件(如未登录)直接返回错误响应
缓存兜底自定义缓存逻辑,命中则直接返回,避免网络请求
请求去重/节流相同请求短时间内重复发起,拦截后直接返回已有响应

注意事项

  • 短路后后续拦截器(包括重试、缓存、连接等)都不会执行
  • 需要手动构造完整的Response对象,包含必要字段(code、protocol、body等)
  • 应用拦截器短路会影响后续所有处理,网络拦截器短路则只影响本次网络请求

Q8:OkHttp如何处理HTTPS(自签名证书/非标准证书)?

答案

默认行为:OkHttp使用系统信任的证书库验证HTTPS,遇到自签名证书会抛出SSLHandshakeException

信任自签名证书的几种方案

方案1:信任所有证书(⚠️ 仅用于测试环境

private fun getUnsafeOkHttpClient(): OkHttpClient {
    // 1. 创建信任所有证书的TrustManager
    val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
        override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {}
        override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {}
        override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
    })
    
    // 2. 创建SSLContext,使用上述TrustManager
    val sslContext = SSLContext.getInstance("TLS")
    sslContext.init(null, trustAllCerts, SecureRandom())
    
    // 3. 创建HostnameVerifier,信任所有hostname
    val hostnameVerifier = HostnameVerifier { _, _ -> true }
    
    return OkHttpClient.Builder()
        .sslSocketFactory(sslContext.socketFactory, trustAllCerts[0] as X509TrustManager)
        .hostnameVerifier(hostnameVerifier)
        .build()
}

方案2:信任特定自签名证书(推荐)

fun getOkHttpClientWithCert(context: Context, certRawResId: Int): OkHttpClient {
    // 1. 加载自签名证书
    val certificateFactory = CertificateFactory.getInstance("X.509")
    val inputStream = context.resources.openRawResource(certRawResId)
    val certificate = certificateFactory.generateCertificate(inputStream)
    inputStream.close()
    
    // 2. 创建KeyStore并添加证书
    val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
    keyStore.load(null, null)
    keyStore.setCertificateEntry("self_signed", certificate)
    
    // 3. 创建TrustManager
    val trustManagerFactory = TrustManagerFactory.getInstance(
        TrustManagerFactory.getDefaultAlgorithm()
    )
    trustManagerFactory.init(keyStore)
    
    // 4. 构建OkHttpClient
    val sslContext = SSLContext.getInstance("TLS")
    sslContext.init(null, trustManagerFactory.trustManagers, SecureRandom())
    
    return OkHttpClient.Builder()
        .sslSocketFactory(sslContext.socketFactory, 
                          trustManagerFactory.trustManagers[0] as X509TrustManager)
        .build()
}

安全性建议

  • 生产环境禁止信任所有证书(MITM攻击风险)
  • 推荐将自签名证书打包进APK,按方案2实现
  • 或使用Certificate Pinning进一步防范中间人攻击

Q9:什么是StreamAllocation?它在OkHttp中的作用是什么?

答案

StreamAllocation是OkHttp中连接管理的核心协调者,贯穿整个请求生命周期。

核心职责

  1. 连接获取与释放:从ConnectionPool获取/创建RealConnection,管理StreamAllocationRealConnection的关联
  2. 引用计数管理:每个RealConnection持有StreamAllocation引用列表,streamAllocationCount决定连接是否空闲
  3. HTTP流管理:协调HttpCodec(编码器)与RealConnection的关系

生命周期

┌─────────────────────────────────────────────────────────────┐
│                    请求生命周期                               │
├─────────────────────────────────────────────────────────────┤
│  1. 在RetryAndFollowUpInterceptor创建StreamAllocation        │
│                           │                                  │
│                           ▼                                  │
│  2. ConnectInterceptor调用streamAllocation.newStream()       │
│      ├─ 尝试从连接池获取RealConnection                        │
│      └─ 无则创建新RealConnection → acquire()增加引用计数       │
│                           │                                  │
│                           ▼                                  │
│  3. CallServerInterceptor使用HttpCodec进行Socket I/O         │
│                           │                                  │
│                           ▼                                  │
│  4. 请求完成后调用streamAllocation.release()                 │
│      └─ 引用计数减1,变为0时连接变为空闲,可被清理              │
└─────────────────────────────────────────────────────────────┘

源码体现

// StreamAllocation.kt
class StreamAllocation(
    val connectionPool: RealConnectionPool,
    val address: Address,
    val call: Call
) {
    // 当前关联的连接
    var connection: RealConnection? = null
    
    fun newStream(): HttpCodec { ... }
    fun release() { ... }
}

StreamAllocation在RetryAndFollowUpInterceptor中创建,在整个请求过程中传递,确保连接的正确复用和释放。

Q10:OkHttp的Dispatcher(分发器)是如何工作的?同步请求和异步请求的区别是什么?

答案

Dispatcher维护请求队列和线程池,控制并发请求数量。

核心数据结构

// Dispatcher.kt
class Dispatcher {
    // 异步请求:正在执行的队列
    private val runningAsyncCalls = ArrayDeque<AsyncCall>()
    // 异步请求:等待执行的队列
    private val readyAsyncCalls = ArrayDeque<AsyncCall>()
    // 同步请求:正在执行的队列
    private val runningSyncCalls = ArrayDeque<RealCall>()
    
    // 并发配置(可调整)
    var maxRequests = 64           // 最大总请求数
    var maxRequestsPerHost = 5     // 每个Host最大并发数
}

同步请求(execute)

override fun execute(): Response {
    // 将当前Call加入runningSyncCalls
    client.dispatcher.executed(this)
    try {
        return getResponseWithInterceptorChain()
    } finally {
        // 请求完成后从队列移除
        client.dispatcher.finished(this)
    }
}

异步请求(enqueue)

override fun enqueue(responseCallback: Callback) {
    // 包装为AsyncCall并提交给Dispatcher
    client.dispatcher.enqueue(AsyncCall(responseCallback))
}

调度策略

                         ┌─────────────────────┐
                         │   enqueue(request)   │
                         └──────────┬──────────┘
                                    │
                                    ▼
              ┌─────────────────────────────────────┐
              │  runningAsyncCalls.size < maxRequests│
              │  AND perHost计数 < maxRequestsPerHost │
              └─────────────────────────────────────┘
                       │                    │
                      Yes                    No
                       │                    │
                       ▼                    ▼
            ┌──────────────────┐   ┌──────────────────┐
            │ 加入running队列   │   │ 加入ready队列     │
            │ 提交线程池执行    │   │ 等待调度          │
            └──────────────────┘   └──────────────────┘

线程池配置

// 默认线程池:可缓存的线程池,无核心线程,闲置60秒回收
executorService = ThreadPoolExecutor(
    0, Int.MAX_VALUE, 60L, TimeUnit.SECONDS,
    SynchronousQueue()
)

调整并发策略

val client = OkHttpClient.Builder()
    .dispatcher(Dispatcher().apply {
        maxRequests = 100
        maxRequestsPerHost = 10
    })
    .build()

Q11:如何在拦截器中正确处理Response的资源释放?

答案

Response中的响应体(ResponseBody)是一个包装了网络流的资源,必须正确关闭。

常见错误:在拦截器中消费了ResponseBody但不重新构建,导致上层无法读取。

正确处理方式

class LoggingInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val response = chain.proceed(request)
        
        // ❌ 错误:直接消费body,会导致上层无法读取
        // val bodyString = response.body?.string()
        
        // ✅ 正确:clone一份用于消费
        val responseBody = response.body
        val bodyString = responseBody?.peekBody(Long.MAX_VALUE)?.string()
        Log.d("OkHttp", "Response: $bodyString")
        
        // ✅ 正确:重新构建Response,保留原始body
        return response.newBuilder()
            .body(responseBody)
            .build()
    }
}

关键点

  • ResponseBody.string()会关闭流,一次请求只能调用一次
  • 使用peekBody()不会关闭原始流,但会消耗内存
  • 返回时必须确保Response的body可被上层正常读取

Q12:应用拦截器(addInterceptor)和网络拦截器(addNetworkInterceptor)有什么区别?

答案

维度应用拦截器网络拦截器
执行位置责任链最开头(index 0)ConnectInterceptor之后,CallServerInterceptor之前
执行次数一次请求只执行一次可能执行多次(重试/重定向时)
能否访问网络中间结果❌ 只能看到最终Response✅ 可看到重定向/重试的中间Response
适用场景添加公共Header、日志、缓存、统计请求压缩、重试逻辑、修改网络层参数
是否受重定向影响不受影响,只拦截最终结果每次网络请求都拦截

执行顺序图示

Request → [应用拦截器][RetryAndFollowUp][Bridge][Cache][Connect][网络拦截器][CallServer] → Socket
                                                                    ↑
                                                            网络拦截器在此之后执行

源码体现

// RealCall.getResponseWithInterceptorChain()
val interceptors = mutableListOf<Interceptor>()
interceptors += client.interceptors          // 应用拦截器最先添加
interceptors += RetryAndFollowUpInterceptor(...)
interceptors += BridgeInterceptor(...)
interceptors += CacheInterceptor(...)
interceptors += ConnectInterceptor(...)
if (!forWebSocket) {
    interceptors += client.networkInterceptors  // 网络拦截器在此添加
}
interceptors += CallServerInterceptor(...)

选择建议

  • 需要全局统一添加Header → 应用拦截器
  • 需要拦截重定向/重试的中间请求 → 网络拦截器
  • 需要打印真正的网络请求/响应细节 → 网络拦截器

Q13:OkHttp的HTTP/2连接合并(Connection Coalescing)是什么?

答案

HTTP/2连接合并是指多个域名可以共享同一个HTTP/2 TCP连接的特性。

原理:如果多个域名解析到相同的IP地址,并且都使用同一个可信证书(或证书覆盖这些域名),OkHttp会自动将这些域名的请求合并到同一个HTTP/2连接上。

触发条件

  1. 服务端支持HTTP/2
  2. 域名解析到相同IP地址
  3. 证书覆盖所有相关域名(如通配符证书 *.example.com 或 SAN扩展包含多个域名)

优势

  • 显著减少TCP连接数
  • 避免多次TCP慢启动
  • 减少握手开销(TLS、TCP)

示例

api.example.com  ──┐
cdn.example.com  ──┼── 同一IP + 同一证书 ──→ 共享一个HTTP/2连接
auth.example.com ──┘

OkHttp内部通过Http2Connection管理连接,使用Http2Connection.combine()判断是否可合并。

Q14:OkHttp默认的SocketFactory和HostnameVerifier是如何工作的?如何自定义?

答案

默认SocketFactory

  • 使用SSLSocketFactory.getDefault()获取系统默认实现
  • 执行TLS握手、证书链验证
  • 证书验证失败会抛出SSLPeerUnverifiedException

默认HostnameVerifier

  • 实现为OkHostnameVerifier
  • 验证规则:
    • 精确域名匹配
    • 通配符匹配(*.example.com 匹配 api.example.com
    • 不匹配则抛出SSLPeerUnverifiedException

自定义示例

// 1. 自定义HostnameVerifier(放宽验证规则)
val customHostnameVerifier = HostnameVerifier { hostname, session ->
    // 允许特定域名通过验证
    hostname == "192.168.1.100" || 
    hostname.endsWith(".internal.company.com") ||
    OkHostnameVerifier.verify(hostname, session)  // 默认逻辑兜底
}

// 2. 自定义SSLSocketFactory(协议/TLS版本限制)
val sslContext = SSLContext.getInstance("TLSv1.2")
sslContext.init(null, null, null)
val socketFactory = sslContext.socketFactory

val client = OkHttpClient.Builder()
    .sslSocketFactory(socketFactory, trustManager)
    .hostnameVerifier(customHostnameVerifier)
    .build()

注意:自定义sslSocketFactory时必须同时传入匹配的TrustManager,否则会导致证书验证异常。

Q15:OkHttp如何处理WebSocket连接?

答案

OkHttp原生支持WebSocket协议,通过RealWebSocket实现。

基本使用

val request = Request.Builder()
    .url("ws://echo.websocket.org")
    .build()

val webSocket = client.newWebSocket(request, object : WebSocketListener() {
    override fun onOpen(webSocket: WebSocket, response: Response) {
        webSocket.send("Hello!")        // 发送消息
        webSocket.close(1000, "Bye")    // 关闭连接
    }
    
    override fun onMessage(webSocket: WebSocket, text: String) {
        println("收到消息: $text")
    }
    
    override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
        // 处理失败
    }
})

底层机制

  1. 通过CallServerInterceptor将HTTP请求升级为WebSocket(通过Upgrade Header)
  2. RealWebSocket管理WebSocket帧的发送和接收
  3. 支持ping/pong心跳保活
  4. 支持消息队列,在网络恢复后自动重发

与普通HTTP请求的区别

  • WebSocket使用独立于拦截器链的WebSocketWriter/Reader
  • 不会经过连接池复用逻辑(WebSocket是长连接,需单独管理)
  • 支持双向实时通信

Q16:OkHttp 同步和异步请求的区别?

答案

特性同步请求 (execute)异步请求 (enqueue)
线程阻塞阻塞直到响应返回不阻塞
调用线程必须在子线程可在任意线程
响应获取直接返回 Response通过 Callback 回调
主线程调用导致 ANR安全
异常处理调用处 try-catch回调的 onFailure

流程图

flowchart LR
    subgraph 同步请求
        A1[调用 execute] --> A2[线程阻塞等待] --> A3[返回 Response]
    end
    
    subgraph 异步请求
        B1[调用 enqueue] --> B2[立即返回] --> B3[后台线程执行请求] --> B4[回调通知]
    end

源码对比

// RealCall.kt - 同步
override fun execute(): Response {
    client.dispatcher.executed(this)
    return getResponseWithInterceptorChain()
}

// RealCall.kt - 异步
override fun enqueue(responseCallback: Callback) {
    client.dispatcher.enqueue(AsyncCall(responseCallback))
}

Q17:分发器(Dispatcher)的工作原理是什么?

答案

Dispatcher 负责管理请求队列和线程池,维护三个队列:

// Dispatcher.kt
private val readyAsyncCalls = ArrayDeque<AsyncCall>()   // 等待队列
private val runningAsyncCalls = ArrayDeque<AsyncCall>() // 运行队列
private val runningSyncCalls = ArrayDeque<RealCall>()   // 同步队列

核心参数

  • maxRequests = 64(最大并发)
  • maxRequestsPerHost = 5(同主机最大并发)

流程图

flowchart TB
    subgraph Dispatcher
        direction TB
        A[异步请求 enqueue] --> B{并发数检查}
        B -->|未达上限| C[runningAsyncCalls<br/>运行队列]
        B -->|已达上限| D[readyAsyncCalls<br/>等待队列]
        C --> E[线程池执行]
        E --> F[请求完成]
        F --> G[从等待队列取出请求执行]
        
        H[同步请求 execute] --> I[runningSyncCalls<br/>同步队列]
        I --> J[当前线程直接执行]
    end

异步请求入队逻辑

// Dispatcher.kt
fun enqueue(call: AsyncCall) {
    if (runningAsyncCalls.size < maxRequests && 
        runningCallsForHost(call) < maxRequestsPerHost) {
        runningAsyncCalls.add(call)
        executorService.execute(call)  // 满足条件,立即执行
    } else {
        readyAsyncCalls.add(call)      // 不满足,进入等待队列
    }
}

Q18:Dispatcher 内部用的什么线程池?

答案

// Dispatcher.kt
fun executorService(): ExecutorService {
    if (executorService == null) {
        executorService = ThreadPoolExecutor(
            0, Int.MAX_VALUE, 60L, TimeUnit.SECONDS,
            SynchronousQueue(), 
            Util.threadFactory("OkHttp Dispatcher", false)
        )
    }
    return executorService
}

流程图

flowchart LR
    subgraph 线程池特点
        T1[核心线程=0] --> T2[所有线程都是临时线程]
        T3[SynchronousQueue] --> T4[无缓冲,直接提交]
        T5[空闲60秒回收] --> T6[节省资源]
    end
参数说明
核心线程数0无常驻线程
最大线程数Integer.MAX_VALUE理论上无限
空闲存活时间60秒线程空闲后回收
工作队列SynchronousQueue无容量,直接提交

Q19:同步请求 execute() 受 Dispatcher 控制吗?

答案

不受。同步请求只被记录到 runningSyncCalls 中用于统计和finished回调,不受并发数限制:

// RealCall.kt
override fun execute(): Response {
    synchronized(this) {
        if (executed) throw IllegalStateException("Already Executed")
        executed = true
    }
    client.dispatcher.executed(this)  // 仅加入runningSyncCalls
    try {
        return getResponseWithInterceptorChain()
    } finally {
        client.dispatcher.finished(this)  // 移除并触发等待队列
    }
}

Q20:Call 对象是什么?可以执行多次吗?

答案

Call 代表一个准备就绪的请求,是请求的执行句柄。只能执行一次

// RealCall.kt
override fun enqueue(responseCallback: Callback) {
    synchronized(this) {
        if (executed) throw IllegalStateException("Already Executed")
        executed = true
    }
    // ...
}

如需重复请求,需调用 client.newCall(request) 创建新的 Call 实例。

Q21:OkHttp 整体请求流程是怎样的?

答案

流程图

flowchart TB
    A[构建 OkHttpClient] --> B[构建 Request]
    B --> C[client.newCall]
    C --> D{execute or enqueue?}
    
    D -->|同步| E[Dispatcher.executed<br/>加入runningSyncCalls]
    D -->|异步| F[Dispatcher.enqueue<br/>调度线程池执行]
    
    E --> G[getResponseWithInterceptorChain]
    F --> G
    
    G --> H[拦截器链处理]
    H --> I[返回 Response]
    
    I --> J[Dispatcher.finished<br/>清理并触发下一个请求]

核心入口源码

// RealCall.kt
fun getResponseWithInterceptorChain(): Response {
    val interceptors = mutableListOf<Interceptor>()
    interceptors += client.interceptors           // 1. 应用拦截器
    interceptors += RetryAndFollowUpInterceptor   // 2. 重试重定向
    interceptors += BridgeInterceptor             // 3. 桥接
    interceptors += CacheInterceptor              // 4. 缓存
    interceptors += ConnectInterceptor            // 5. 连接
    interceptors += client.networkInterceptors    // 6. 网络拦截器
    interceptors += CallServerInterceptor         // 7. 请求服务器
    
    val chain = RealInterceptorChain(interceptors, 0, originalRequest, ...)
    return chain.proceed(originalRequest)
}

Q22:拦截器的设计模式及作用?

答案

采用责任链模式(Chain of Responsibility)。

接口定义

public interface Interceptor {
    Response intercept(Chain chain) throws IOException;
    
    interface Chain {
        Request request();
        Response proceed(Request request) throws IOException;  // 传递给下一节点
    }
}

流程图

flowchart LR
    subgraph 责任链模式
        direction LR
        R[Request] --> I1[拦截器1]
        I1 --> I2[拦截器2]
        I2 --> I3[拦截器3]
        I3 --> I4[...]
        I4 --> S[Server]
        S --> I4
        I4 --> I3
        I3 --> I2
        I2 --> I1
        I1 --> Resp[Response]
    end

Q23:OkHttp 默认的拦截器链包含哪些?

答案

流程图

flowchart TB
    subgraph 拦截器链
        A[应用拦截器<br/>addInterceptor] 
        B[RetryAndFollowUpInterceptor<br/>重试与重定向]
        C[BridgeInterceptor<br/>桥接: 添加Header/Cookie/GZIP]
        D[CacheInterceptor<br/>缓存处理]
        E[ConnectInterceptor<br/>连接池获取/建立连接]
        F[网络拦截器<br/>addNetworkInterceptor]
        G[CallServerInterceptor<br/>真实I/O写入/读取]
        
        A --> B --> C --> D --> E --> F --> G
    end
    
    subgraph 响应回传
        G -->|响应原路返回| F --> E --> D --> C --> B --> A
    end
顺序拦截器核心职责
0应用拦截器(自定义)业务层全局处理
1RetryAndFollowUpInterceptor重试与重定向
2BridgeInterceptor添加请求头、Cookie、GZIP解压
3CacheInterceptor缓存处理
4ConnectInterceptor连接池获取/建立连接
5网络拦截器(自定义)网络层全局处理
6CallServerInterceptor真实I/O写入/读取

Q24:拦截链 chain.proceed(request) 是干嘛的?

答案

chain.proceed(request) 用于将请求传递给拦截器链中的下一个节点,是责任链模式的核心。

源码

// RealInterceptorChain.kt
fun proceed(request: Request): Response {
    // 创建下一个链节点(index+1)
    val next = RealInterceptorChain(interceptors, index + 1, request, ...)
    
    // 获取当前拦截器,调用其intercept方法,并传入下一个链
    val interceptor = interceptors[index]
    val response = interceptor.intercept(next)
    
    return response
}

关键点:不调用 proceed,请求就会被拦截,不会走到服务器。

Q25:连接池架构是怎样的?

答案

架构图

flowchart TB
    subgraph ConnectionPool
        C1[connections<br/>Deque<RealConnection>]
        C2[CleanupRunnable<br/>清理线程]
        C3[maxIdleConnections=5]
        C4[keepAliveDuration=5min]
    end
    
    subgraph 连接复用流程
        R1[新请求到达] --> R2{从连接池查找}
        R2 -->|找到| R3[复用连接]
        R2 -->|未找到| R4[创建新连接]
        R4 --> R5[建立TCP连接]
        R5 --> R6[放入连接池]
        R6 --> R7[请求执行]
        R3 --> R7
    end
    
    subgraph 连接清理
        R7 --> C8[请求完成]
        C8 --> C9[连接放回池]
        C9 --> C10{空闲检查}
        C10 -->|空闲>5min或>5个| C11[关闭并移除]
        C10 -->|未超限| C12[保持等待复用]
    end

默认配置

// ConnectionPool.java
public ConnectionPool() {
    this(5, 5, TimeUnit.MINUTES);  // maxIdleConnections=5, keepAlive=5分钟
}

连接存储

public final class ConnectionPool {
    private final Deque<RealConnection> connections = new ArrayDeque<>();
    
    // 获取复用连接
    @Nullable RealConnection get(Address address, StreamAllocation streamAllocation) {
        for (RealConnection connection : connections) {
            if (connection.isEligible(address, streamAllocation)) {
                streamAllocation.acquire(connection, true);
                return connection;
            }
        }
        return null;
    }
}

Q26:连接池是怎么清理空闲连接的?

答案

采用引用计数 + 懒清理机制

// ConnectionPool.java
private final Runnable cleanupRunnable = new Runnable() {
    @Override public void run() {
        while (true) {
            long waitNanos = cleanup(System.nanoTime());
            if (waitNanos == -1) return;
            if (waitNanos > 0) {
                synchronized (ConnectionPool.this) {
                    ConnectionPool.this.wait(waitNanos / 1000000, (int) (waitNanos % 1000000));
                }
            }
        }
    }
};

清理条件

  • 空闲超过 5 分钟
  • 空闲连接数 > 5

引用计数实现

// RealConnection.java
public final List<Reference<StreamAllocation>> allocations = new ArrayList<>();
// allocations.size() == 0 表示空闲连接

Q27:一个连接可以同时发多个请求吗?

答案

取决于 HTTP 协议版本:

flowchart LR
    subgraph HTTP/1.1
        H1[请求1] --> S1[连接1] --> R1[响应1]
        H2[请求2] --> S2[连接2] --> R2[响应2]
        H3[请求3] --> S3[连接3] --> R3[响应3]
    end
    
    subgraph HTTP/2
        H4[请求1] --> S4[单个连接]
        H5[请求2] --> S4
        H6[请求3] --> S4
        S4 -->|多路复用| R4[响应1]
        S4 -->|多路复用| R5[响应2]
        S4 -->|多路复用| R6[响应3]
    end
  • HTTP/1.1:不行,串行(队头阻塞)
  • HTTP/2:可以,多路复用(二进制帧交错传输)

Q28:缓存流程图是怎样的?

答案

flowchart TB
    A[发起请求] --> B{CacheInterceptor}
    B --> C[查找本地缓存]
    
    C --> D{缓存是否存在?}
    D -->|否| E[发起网络请求]
    D -->|是| F{缓存是否过期?}
    
    F -->|未过期| G[直接返回缓存]
    F -->|已过期| H[发送条件请求<br/>If-Modified-Since/If-None-Match]
    
    H --> I{服务器响应}
    I -->|304 Not Modified| J[返回缓存并更新过期时间]
    I -->|200 OK| K[返回新数据并更新缓存]
    
    E --> L[获取网络响应]
    L --> M{响应头Cache-Control?}
    M -->|允许缓存| N[存入DiskLruCache]
    M -->|不允许缓存| O[不存储]
    N --> P[返回响应]
    O --> P
    G --> Q[结束]
    J --> Q
    K --> Q
    P --> Q

核心源码

// CacheInterceptor.java
@Override public Response intercept(Chain chain) throws IOException {
    Response cacheCandidate = cache != null ? cache.get(chain.request()) : null;
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    
    // 网络请求为null,直接用缓存
    if (strategy.networkRequest == null && strategy.cacheResponse == null) {
        return new Response.Builder().code(504).build();
    }
    if (strategy.networkRequest == null) {
        return strategy.cacheResponse;
    }
    
    Response networkResponse = chain.proceed(strategy.networkRequest);
    // 处理304等...
    return networkResponse;
}

Q29:缓存策略判断流程是怎样的?

答案

flowchart TB
    A[请求头 + 缓存响应头] --> B[解析Cache-Control]
    B --> C{是否有no-cache?}
    C -->|是| D[强制走网络]
    C -->|否| E{是否过期?}
    E -->|未过期| F[使用缓存]
    E -->|已过期| G{是否有ETag/Last-Modified?}
    G -->|有| H[生成条件请求]
    G -->|无| D
    H --> I[发送If-None-Match/If-Modified-Since]

Cache-Control 常用指令

  • max-age=3600:缓存有效期1小时
  • no-cache:每次需验证
  • no-store:不缓存
  • must-revalidate:过期后必须验证

Q30:缓存存在哪?默认多大?

答案

  • 存在文件系统,内部是 DiskLruCache
  • 默认不开启,必须手动指定 Cache
  • 一般设置 10MB~100MB
val cache = Cache(cacheDir, 50 * 1024 * 1024)  // 50MB
val client = OkHttpClient.Builder().cache(cache).build()

Q31:如何配置缓存?

答案

// 1. 创建Cache对象
val cacheDir = File(context.cacheDir, "okhttp_cache")
val cache = Cache(cacheDir, 50 * 1024 * 1024)  // 50MB

// 2. 配置到OkHttpClient
val client = OkHttpClient.Builder()
    .cache(cache)
    .build()

// 3. 请求时可通过Cache-Control控制
val request = Request.Builder()
    .url(url)
    .header("Cache-Control", "max-age=3600")  // 强制缓存1小时
    .build()

Q32:缓存失效后,OkHttp 会怎么做?

答案

发送条件请求(Conditional Request):

// CacheStrategy.java
if (cacheResponse != null) {
    String etag = cacheResponse.header("ETag");
    if (etag != null) {
        requestBuilder.header("If-None-Match", etag);
    }
    String lastModified = cacheResponse.header("Last-Modified");
    if (lastModified != null) {
        requestBuilder.header("If-Modified-Since", lastModified);
    }
}
服务端响应行为
304 Not Modified复用旧缓存,更新过期时间
200 OK使用新数据,更新缓存

Q33:HTTPS 握手与证书验证流程是怎样的?

答案

sequenceDiagram
    participant Client as OkHttp Client
    participant Server as Server
    
    Client->>Server: ClientHello (支持的TLS版本、加密套件)
    Server->>Client: ServerHello + 证书链
    Client->>Client: 验证证书有效性
    Client->>Client: 1. 是否在有效期内?
    Client->>Client: 2. 是否被信任CA签发?
    Client->>Client: 3. 域名是否匹配(HostnameVerifier)
    Client->>Client: 4. 是否通过证书锁定(CertificatePinner)
    
    alt 验证通过
        Client->>Server: 生成并加密PreMaster Secret
        Server->>Client: 加密通信开始
        Note over Client,Server: 后续通信加密
    else 验证失败
        Client->>Client: 抛出SSLException
    end

Q34:OkHttp 如何支持 HTTP/2?

答案

OkHttpClient.Builder 中可配置协议,自动协商:

// OkHttpClient.Builder
public Builder() {
    protocols = Arrays.asList(Protocol.HTTP_2, Protocol.HTTP_1_1);
    // ...
}

协商流程

  1. 客户端发送请求时通过 ALPN(Application-Layer Protocol Negotiation)告知支持的协议列表
  2. 服务端选择 HTTP/2 或降级到 HTTP/1.1
  3. 若支持 HTTP/2,则复用单个 TCP 连接实现多路复用

Q35:GZIP 压缩机制?

答案

BridgeInterceptor 自动添加请求头并自动解压响应:

// BridgeInterceptor.java
if (userRequest.header("Accept-Encoding") == null) {
    requestBuilder.header("Accept-Encoding", "gzip");
}

收到响应后自动解压,对上层透明。可通过移除 Accept-Encoding 头禁用。

Q36:如何处理 Cookie?

答案

实现 CookieJar 接口:

class PersistentCookieJar : CookieJar {
    private val cookieStore = ConcurrentHashMap<String, List<Cookie>>()
    
    override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
        cookieStore[url.host] = cookies
    }
    
    override fun loadForRequest(url: HttpUrl): List<Cookie> {
        return cookieStore[url.host] ?: emptyList()
    }
}

val client = OkHttpClient.Builder()
    .cookieJar(PersistentCookieJar())
    .build()

默认 CookieJar.NO_COOKIES(不存储Cookie)。

Q37:什么是证书锁定(Certificate Pinning)?

答案

证书锁定是防范中间人攻击(MITM)的安全机制,将服务器证书的公钥哈希值预置在客户端,只信任匹配的证书。

val certificatePinner = CertificatePinner.Builder()
    .add("example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
    .add("example.com", "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=")  // 备用
    .build()

val client = OkHttpClient.Builder()
    .certificatePinner(certificatePinner)
    .build()

注意:需提供至少两个 pin(主备),避免证书更新导致不可用。

Q38:HostnameVerifier 是干嘛的?

答案

验证证书中的域名和请求的 host 是否一致,防止证书被冒用。

public interface HostnameVerifier {
    boolean verify(String hostname, SSLSession session);
}

默认实现 OkHostnameVerifier 支持:

  • 精确域名匹配
  • 通配符匹配(*.example.com 匹配 api.example.com
  • IP 地址匹配

Q39:哪些异常属于 IO 异常,哪些是业务异常?

答案

类型异常/状态说明
网络层异常SocketTimeoutException连接/读超时
网络层异常ConnectException连接被拒绝
网络层异常SSLExceptionSSL握手失败
网络层异常IOException其他IO错误
业务状态码4xx/5xx不算异常Response.isSuccessful() 返回 false
// 正确区分
call.enqueue(object : Callback {
    override fun onFailure(call: Call, e: IOException) {
        // 网络层异常
    }
    override fun onResponse(call: Call, response: Response) {
        if (response.isSuccessful) {
            // 2xx
        } else {
            // 4xx/5xx 业务错误,非异常
        }
    }
})

Q40:什么时候会触发重试?

答案

RetryAndFollowUpInterceptor 负责,通过 while(true) 循环实现:

触发场景

  1. 路由异常(RouteException):连接失败可恢复时
  2. IO异常(IOException):如 SocketTimeoutException 且可重试
  3. 重定向(3xx 状态码):最多 20 次
  4. 认证失败(401):需要重新认证

重试条件判断

// RetryAndFollowUpInterceptor.kt
private fun isRecoverable(e: IOException, requestSendStarted: Boolean): Boolean {
    // 协议异常不可恢复
    if (e is ProtocolException) return false
    // 请求体已发送时不可恢复(避免重复发送)
    if (requestSendStarted && e is InterruptedIOException) return false
    return true
}

Q41:OkHttp 相比其他框架的优势?

答案

优势说明
HTTP/2 多路复用单连接并发处理多请求
连接池复用减少 TCP 握手开销
GZIP 默认压缩节省流量
自动重试与恢复提高成功率
简洁 API易于使用和扩展
拦截器机制灵活定制请求/响应处理
平台适配Android 4.4+ / Java 8+

Q42:如何优化 OkHttp 的网络性能?

答案

  1. 复用 OkHttpClient(单例模式)——最重要
  2. 合理配置 Dispatcher 并发数
  3. 调整 ConnectionPool 参数
  4. 启用缓存
  5. 开启 HTTP/2
  6. 添加重试机制
  7. 及时取消请求防止内存泄漏
// 单例模式
object OkHttpManager {
    val client: OkHttpClient by lazy {
        OkHttpClient.Builder()
            .connectTimeout(10, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .writeTimeout(30, TimeUnit.SECONDS)
            .retryOnConnectionFailure(true)
            .cache(Cache(context.cacheDir, 50 * 1024 * 1024))
            .dispatcher(Dispatcher().apply {
                maxRequests = 100
                maxRequestsPerHost = 10
            })
            .build()
    }
}

Q43:如何解决 OkHttp 引发的内存泄漏?

答案

在 Activity/Fragment 销毁时取消请求:

@Override protected void onDestroy() {
    super.onDestroy();
    // 方式1:取消单个请求
    call.cancel();
    
    // 方式2:按tag批量取消
    okHttpClient.dispatcher().cancelAllWithTag(this);
}

最佳实践

  • 回调中使用弱引用持有 Activity/View
  • 使用 tag 管理请求生命周期
  • 在 ViewModel 中执行请求,避免直接持有 View 引用

Q44:OkHttp 中使用了哪些设计模式?

答案

设计模式图

flowchart TB
    subgraph 建造者模式
        B1[OkHttpClient.Builder] --> B2[构建OkHttpClient]
        B3[Request.Builder] --> B4[构建Request]
    end
    
    subgraph 责任链模式
        C1[Interceptor] --> C2[拦截器1]
        C1 --> C3[拦截器2]
        C1 --> C4[拦截器3]
        C2 --> C5[proceed传递]
    end
    
    subgraph 工厂模式
        F1[OkHttpClient] --> F2[newCall] --> F3[RealCall]
    end
    
    subgraph 策略模式
        S1[Dispatcher] --> S2[调度策略: maxRequests/maxRequestsPerHost]
    end
    
    subgraph 外观模式
        G1[OkHttpClient] --> G2[封装: 连接池/拦截器/缓存]
    end
设计模式体现位置
建造者模式OkHttpClient.BuilderRequest.Builder
责任链模式拦截器链
工厂模式newCall() 创建 Call
策略模式Dispatcher 的调度策略
观察者模式异步请求回调
单例模式ConnectionPool 全局共享
外观模式OkHttpClient 封装复杂子系统

Q45:OkHttp 完整架构总览是怎样的?

答案

flowchart TB
    subgraph 用户层
        U1[Application] --> U2[Retrofit]
        U2 --> U3[OkHttpClient]
    end
    
    subgraph OkHttp核心
        direction TB
        O1[Request] --> O2[RealCall]
        O2 --> O3{Dispatcher}
        
        O3 -->|同步| O4[runningSyncCalls]
        O3 -->|异步| O5[runningAsyncCalls/readyAsyncCalls]
        O5 --> O6[ThreadPool]
        
        O4 --> O7[拦截器链]
        O6 --> O7
        
        subgraph 拦截器链
            direction LR
            I1[应用拦截器] --> I2[RetryAndFollowUp] --> I3[Bridge] --> I4[Cache] --> I5[Connect] --> I6[网络拦截器] --> I7[CallServer]
        end
        
        O7 --> O8[ConnectionPool]
        O8 --> O9[RealConnection]
    end
    
    subgraph 底层
        L1[Socket] --> L2[网络]
    end
    
    O9 --> L1
    L2 --> U1
    
    subgraph 缓存层
        C1[DiskLruCache] --> C2[文件系统]
    end
    
    I4 --> C1

核心源码文件速查表

类名文件位置核心职责
RealCallRealCall.kt请求执行入口,getResponseWithInterceptorChain()
RealInterceptorChainRealInterceptorChain.kt责任链核心,递归调用拦截器
RetryAndFollowUpInterceptorRetryAndFollowUpInterceptor.kt重试重定向
BridgeInterceptorBridgeInterceptor.kt请求头转换、Gzip解压
CacheInterceptorCacheInterceptor.ktHTTP缓存策略
ConnectInterceptorConnectInterceptor.kt连接池获取连接
CallServerInterceptorCallServerInterceptor.ktSocket I/O
ConnectionPoolConnectionPool.kt连接池管理、空闲清理
RealConnectionRealConnection.kt封装Socket连接
StreamAllocationStreamAllocation.kt连接协调器、引用计数
DispatcherDispatcher.kt请求队列调度
RealWebSocketRealWebSocket.ktWebSocket协议实现

完整知识点速查表

编号知识点核心要点
Q1责任链模式递归调用,index递增,先进后出
Q2内置拦截器7个拦截器,各司其职
Q3缓存拦截器强缓存/协商缓存,DiskLruCache
Q4连接池复用Address匹配,引用计数,5连接/5分钟清理
Q5单连接并发HTTP/1.1不支持,HTTP/2支持多路复用
Q6取消请求cancel() + tag批量取消
Q7拦截器短路直接返回Response,不调用proceed
Q8HTTPS自签名自定义SSLContext + HostnameVerifier
Q9StreamAllocation连接协调器,引用计数管理
Q10Dispatcher三队列,maxRequests=64,maxPerHost=5
Q11Response资源释放peekBody()不关闭流,需重新构建
Q12应用vs网络拦截器位置、次数、缓存影响不同
Q13HTTP/2连接合并同IP+同证书共享连接
Q14SocketFactory系统默认,可自定义协议/TLS版本
Q15WebSocketRealWebSocket实现,帧管理
Q16同步vs异步阻塞/非阻塞,线程要求
Q17Dispatcher原理并发控制,等待队列
Q18线程池0核心线程,SynchronousQueue
Q19同步请求控制仅记录,不受限
Q20Call对象只能执行一次
Q21整体流程7步拦截器链
Q22设计模式责任链
Q23默认拦截器7个内置拦截器
Q24chain.proceed传递到下一节点
Q25连接池架构Deque存储,清理线程
Q26空闲清理引用计数+懒清理
Q27单连接并发HTTP/2多路复用
Q28缓存流程图CacheInterceptor核心
Q29缓存策略Cache-Control解析
Q30缓存位置DiskLruCache,默认不开启
Q31缓存配置Cache对象手动设置
Q32缓存失效条件请求(ETag/Last-Modified)
Q33HTTPS握手证书验证4步
Q34HTTP/2支持ALPN协商
Q35GZIPBridgeInterceptor自动处理
Q36CookieCookieJar接口实现
Q37证书锁定CertificatePinner防MITM
Q38HostnameVerifier域名与证书匹配验证
Q39异常分类IO异常vs业务状态码
Q40重试触发RetryAndFollowUpInterceptor
Q41框架优势HTTP/2、连接池、GZIP等
Q42性能优化单例、并发配置、缓存
Q43内存泄漏取消请求+弱引用
Q44设计模式7种设计模式
Q45完整架构用户层→核心→底层→缓存

一句话记忆口诀

分发器管并发,拦截器串成链;连接池复 TCP,缓存遵循 HTTP;HTTP/2 多路复用,优化就靠单例和取消。