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

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

答案

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

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

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)  // 启动链式调用
}

流程图

scss 复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                         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() 核心逻辑

kotlin 复制代码
// 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
1 RetryAndFollowUpInterceptor 失败重试、重定向处理(3xx)、最大20次 RetryAndFollowUpInterceptor.kt
2 BridgeInterceptor 请求/响应转换:添加标准Header(Content-Type、Host、Accept-Encoding、Cookie),Gzip解压 BridgeInterceptor.kt
3 CacheInterceptor HTTP缓存策略:强缓存/协商缓存判断,缓存读写 CacheInterceptor.kt
4 ConnectInterceptor 连接池管理:获取/创建连接,Socket建立 ConnectInterceptor.kt
5 网络拦截器 网络层全局处理(可获取真正的网络请求/响应) 自定义Interceptor
6 CallServerInterceptor 实际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写入缓存

源码片段(简化版):

kotlin 复制代码
// 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
}

配置缓存

kotlin 复制代码
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. 连接可用:未被关闭或回收

复用核心代码(简化):

kotlin 复制代码
// 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时连接变为空闲,可被清理

空闲连接清除机制

kotlin 复制代码
// 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多路复用原理

scss 复制代码
┌─────────────────────────────────────────────────────────────┐
│                      单个 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:如何取消一个正在进行的网络请求?取消后的底层发生了什么?

答案

取消方式

kotlin 复制代码
// 方式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")

源码关键路径

kotlin 复制代码
// 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)责任链。

实现方式

kotlin 复制代码
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:信任所有证书(⚠️ 仅用于测试环境

kotlin 复制代码
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:信任特定自签名证书(推荐)

kotlin 复制代码
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的关系

生命周期

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

源码体现

kotlin 复制代码
// 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维护请求队列和线程池,控制并发请求数量。

核心数据结构

kotlin 复制代码
// 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)

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

异步请求(enqueue)

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

调度策略

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

线程池配置

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

调整并发策略

kotlin 复制代码
val client = OkHttpClient.Builder()
    .dispatcher(Dispatcher().apply {
        maxRequests = 100
        maxRequestsPerHost = 10
    })
    .build()

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

答案

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

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

正确处理方式

kotlin 复制代码
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、日志、缓存、统计 请求压缩、重试逻辑、修改网络层参数
是否受重定向影响 不受影响,只拦截最终结果 每次网络请求都拦截

执行顺序图示

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

源码体现

kotlin 复制代码
// 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

自定义示例

kotlin 复制代码
// 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实现。

基本使用

kotlin 复制代码
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

源码对比

kotlin 复制代码
// 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 负责管理请求队列和线程池,维护三个队列:

kotlin 复制代码
// 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
运行队列] B -->|已达上限| D[readyAsyncCalls
等待队列] C --> E[线程池执行] E --> F[请求完成] F --> G[从等待队列取出请求执行] H[同步请求 execute] --> I[runningSyncCalls
同步队列] I --> J[当前线程直接执行] end

异步请求入队逻辑

kotlin 复制代码
// 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 内部用的什么线程池?

答案

kotlin 复制代码
// 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回调,不受并发数限制:

kotlin 复制代码
// 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 代表一个准备就绪的请求,是请求的执行句柄。只能执行一次

kotlin 复制代码
// 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
加入runningSyncCalls] D -->|异步| F[Dispatcher.enqueue
调度线程池执行] E --> G[getResponseWithInterceptorChain] F --> G G --> H[拦截器链处理] H --> I[返回 Response] I --> J[Dispatcher.finished
清理并触发下一个请求]

核心入口源码

kotlin 复制代码
// 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)。

接口定义

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

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

答案

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

源码

kotlin 复制代码
// 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
Deque] C2[CleanupRunnable
清理线程] 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

默认配置

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

连接存储

java 复制代码
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:连接池是怎么清理空闲连接的?

答案

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

java 复制代码
// 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

引用计数实现

java 复制代码
// 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[发送条件请求
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

核心源码

java 复制代码
// 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
kotlin 复制代码
val cache = Cache(cacheDir, 50 * 1024 * 1024)  // 50MB
val client = OkHttpClient.Builder().cache(cache).build()

Q31:如何配置缓存?

答案

kotlin 复制代码
// 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):

java 复制代码
// 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 中可配置协议,自动协商:

java 复制代码
// 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 自动添加请求头并自动解压响应:

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

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

Q36:如何处理 Cookie?

答案

实现 CookieJar 接口:

kotlin 复制代码
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)的安全机制,将服务器证书的公钥哈希值预置在客户端,只信任匹配的证书。

kotlin 复制代码
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 是否一致,防止证书被冒用。

java 复制代码
public interface HostnameVerifier {
    boolean verify(String hostname, SSLSession session);
}

默认实现 OkHostnameVerifier 支持:

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

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

答案

类型 异常/状态 说明
网络层异常 SocketTimeoutException 连接/读超时
网络层异常 ConnectException 连接被拒绝
网络层异常 SSLException SSL握手失败
网络层异常 IOException 其他IO错误
业务状态码 4xx/5xx 不算异常Response.isSuccessful() 返回 false
kotlin 复制代码
// 正确区分
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):需要重新认证

重试条件判断

kotlin 复制代码
// 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. 及时取消请求防止内存泄漏
kotlin 复制代码
// 单例模式
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 销毁时取消请求:

java 复制代码
@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

核心源码文件速查表

类名 文件位置 核心职责
RealCall RealCall.kt 请求执行入口,getResponseWithInterceptorChain()
RealInterceptorChain RealInterceptorChain.kt 责任链核心,递归调用拦截器
RetryAndFollowUpInterceptor RetryAndFollowUpInterceptor.kt 重试重定向
BridgeInterceptor BridgeInterceptor.kt 请求头转换、Gzip解压
CacheInterceptor CacheInterceptor.kt HTTP缓存策略
ConnectInterceptor ConnectInterceptor.kt 连接池获取连接
CallServerInterceptor CallServerInterceptor.kt Socket I/O
ConnectionPool ConnectionPool.kt 连接池管理、空闲清理
RealConnection RealConnection.kt 封装Socket连接
StreamAllocation StreamAllocation.kt 连接协调器、引用计数
Dispatcher Dispatcher.kt 请求队列调度
RealWebSocket RealWebSocket.kt WebSocket协议实现

完整知识点速查表

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

一句话记忆口诀

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

相关推荐
invicinble2 小时前
前端技术栈--webpack
前端·webpack·node.js
天籁晴空2 小时前
微信小程序 静默登录 + 授权登录 双模式配合的设计方案
前端·微信小程序·uni-app
|晴 天|2 小时前
Vue 3 博客 SEO 优化:Meta 标签、Sitemap、Schema.org 实战
前端·vue.js·dreamweaver
Apple_羊先森2 小时前
# MOSS-TTS-Nano 教程 02:CLI 与 Web Demo 实战
前端·人工智能
Bat U2 小时前
JavaEE|多线程(三)
java·前端·java-ee
90后的晨仔11 小时前
Android Studio 项目模板完全指南
android
summerkissyou198711 小时前
Android-SurfaceView-投屏-常见问题
android·surfaceview
明天就是Friday11 小时前
Android实战项目④ OkHttp WebSocket开发即时通讯App 完整源码详解
android·websocket·okhttp
超级无敌暴龙兽11 小时前
和我一起刷面试题呀
前端·面试