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
}
执行特点:
- 请求阶段:拦截器按列表顺序从前向后执行前置处理
- 响应阶段:拦截器按相反顺序(后进先出)从后向前执行后置处理
- 递归机制 :每个拦截器调用
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缓存?
答案:
核心流程:
- 读取候选缓存:根据请求从Cache中获取缓存的Response
- 创建缓存策略 :通过
CacheStrategy.Factory结合请求头(Cache-Control)和缓存的响应头,计算策略 - 策略判断 :
- 强缓存命中 →
networkRequest == null,直接返回cacheResponse - 协商缓存 → 构造带If-Modified-Since/If-None-Match的networkRequest
- 无缓存 → 正常发起网络请求
- 强缓存命中 →
- 缓存响应:网络请求返回后,将新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()判断是否匹配,判断条件包括:
- 地址匹配:host、port、proxy一致
- 协议兼容:HTTP/1.1需支持Keep-Alive,HTTP/2自动支持多路复用
- SSL配置匹配:证书和hostnameVerifier一致
- 连接可用:未被关闭或回收
复用核心代码(简化):
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")
取消原理:
- 设置取消标志 :
Call.cancel()在RealCall中设置canceled标志为true - 中断Socket I/O :取消正在进行的
Exchange(对应底层Socket流) - 清理Dispatcher队列:如果请求在队列中未开始,直接从等待队列移除
- 触发回调 :异步回调的
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中连接管理的核心协调者,贯穿整个请求生命周期。
核心职责:
- 连接获取与释放 :从
ConnectionPool获取/创建RealConnection,管理StreamAllocation与RealConnection的关联 - 引用计数管理 :每个
RealConnection持有StreamAllocation引用列表,streamAllocationCount决定连接是否空闲 - 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连接上。
触发条件:
- 服务端支持HTTP/2
- 域名解析到相同IP地址
- 证书覆盖所有相关域名(如通配符证书
*.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?) {
// 处理失败
}
})
底层机制:
- 通过
CallServerInterceptor将HTTP请求升级为WebSocket(通过Upgrade Header) RealWebSocket管理WebSocket帧的发送和接收- 支持ping/pong心跳保活
- 支持消息队列,在网络恢复后自动重发
与普通HTTP请求的区别:
- WebSocket使用独立于拦截器链的
WebSocketWriter/Reader - 不会经过连接池复用逻辑(WebSocket是长连接,需单独管理)
- 支持双向实时通信
Q16:OkHttp 同步和异步请求的区别?
答案:
| 特性 | 同步请求 (execute) |
异步请求 (enqueue) |
|---|---|---|
| 线程阻塞 | 阻塞直到响应返回 | 不阻塞 |
| 调用线程 | 必须在子线程 | 可在任意线程 |
| 响应获取 | 直接返回 Response | 通过 Callback 回调 |
| 主线程调用 | 导致 ANR | 安全 |
| 异常处理 | 调用处 try-catch | 回调的 onFailure |
流程图:
源码对比:
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(同主机最大并发)
流程图:
运行队列] 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
}
流程图:
| 参数 | 值 | 说明 |
|---|---|---|
| 核心线程数 | 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 整体请求流程是怎样的?
答案:
流程图:
加入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; // 传递给下一节点
}
}
流程图:
Q23:OkHttp 默认的拦截器链包含哪些?
答案:
流程图:
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:连接池架构是怎样的?
答案:
架构图:
Deque
清理线程] 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 协议版本:
- HTTP/1.1:不行,串行(队头阻塞)
- HTTP/2:可以,多路复用(二进制帧交错传输)
Q28:缓存流程图是怎样的?
答案:
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:缓存策略判断流程是怎样的?
答案:
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 握手与证书验证流程是怎样的?
答案:
Q34:OkHttp 如何支持 HTTP/2?
答案:
OkHttpClient.Builder 中可配置协议,自动协商:
java
// OkHttpClient.Builder
public Builder() {
protocols = Arrays.asList(Protocol.HTTP_2, Protocol.HTTP_1_1);
// ...
}
协商流程:
- 客户端发送请求时通过 ALPN(Application-Layer Protocol Negotiation)告知支持的协议列表
- 服务端选择 HTTP/2 或降级到 HTTP/1.1
- 若支持 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) 循环实现:
触发场景:
- 路由异常(RouteException):连接失败可恢复时
- IO异常(IOException):如 SocketTimeoutException 且可重试
- 重定向(3xx 状态码):最多 20 次
- 认证失败(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 的网络性能?
答案:
- 复用 OkHttpClient(单例模式)------最重要
- 合理配置 Dispatcher 并发数
- 调整 ConnectionPool 参数
- 启用缓存
- 开启 HTTP/2
- 添加重试机制
- 及时取消请求防止内存泄漏
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 中使用了哪些设计模式?
答案:
设计模式图:
| 设计模式 | 体现位置 |
|---|---|
| 建造者模式 | OkHttpClient.Builder、Request.Builder |
| 责任链模式 | 拦截器链 |
| 工厂模式 | newCall() 创建 Call |
| 策略模式 | Dispatcher 的调度策略 |
| 观察者模式 | 异步请求回调 |
| 单例模式 | ConnectionPool 全局共享 |
| 外观模式 | OkHttpClient 封装复杂子系统 |
Q45:OkHttp 完整架构总览是怎样的?
答案:
核心源码文件速查表
| 类名 | 文件位置 | 核心职责 |
|---|---|---|
| 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 多路复用,优化就靠单例和取消。