OkHttp 总结

OkHttp 总结

以下是按难度分层整理,覆盖原理、源码和实战场景。


一、基础概念类

Q1:为什么选择 OkHttp?它相比 HttpURLConnection 有什么优势?

答:

  • 连接复用:自动维护连接池,复用 TCP 连接,减少握手开销

  • HTTP/2 支持:多路复用、头部压缩、服务器推送

  • 自动处理:GZIP 压缩、重试重定向、响应缓存

  • 拦截器机制:强大的请求/响应拦截能力

  • 流式 API:基于 Okio 的高效 I/O,减少内存拷贝

  • 现代协议:支持 WebSocket、TLS 1.3


Q2:OkHttp 中有哪些核心类?它们各自的作用是什么?

答:

作用
OkHttpClient 配置中心,管理连接池、拦截器、超时等(Builder 模式,建议单例)
Request 封装 HTTP 请求(URL、Method、Headers、Body)
Response 封装 HTTP 响应(Code、Headers、Body)
Call 代表一次请求,可执行或取消
Dispatcher 调度器,管理异步请求的线程池和并发数
ConnectionPool 连接池,复用 TCP 连接
Interceptor 拦截器接口,用于拦截和修改请求/响应

Q3:同步请求和异步请求的区别?

答:

kotlin

复制代码
// 同步 - 阻塞当前线程,直接返回 Response
val response = client.newCall(request).execute()

// 异步 - 非阻塞,通过 Callback 回调结果
client.newCall(request).enqueue(callback)

区别:

  • 同步 :在当前线程执行,会阻塞,不能在主线程调用 (Android 4.0+ 会抛 NetworkOnMainThreadException

  • 异步 :内部通过 Dispatcher 分配到线程池执行,回调在子线程,需手动切回主线程更新 UI


二、拦截器机制(高频)

Q4:应用拦截器和网络拦截器有什么区别?

答:

维度 应用拦截器 addInterceptor 网络拦截器 addNetworkInterceptor
调用次数 每个请求只调用一次 可能多次(重定向、重试时)
观察内容 应用层看到的请求/响应 实际发送到网络的请求/响应
是否经过缓存 否(在网络层)
是否经过重试
适用场景 日志打印、统一添加 Header、签名 网络调试、监控实际传输数据

执行顺序:

plain

复制代码
应用拦截器1 → 应用拦截器2 → RetryAndFollowUp → Bridge → Cache → Connect → 网络拦截器 → CallServer

Q5:写一个日志拦截器,要注意什么?

答:

kotlin

复制代码
class LoggingInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        
        // 记录请求
        val t1 = System.nanoTime()
        Log.d("OkHttp", "Sending request ${request.url}")
        
        val response = chain.proceed(request)
        
        // 记录响应
        val t2 = System.nanoTime()
        Log.d("OkHttp", "Received response for ${response.request.url} in ${(t2 - t1) / 1e6}ms")
        
        return response
    }
}

注意点:

  • ResponseBody 只能读取一次,读取后需要重新构建 Response 返回

  • 大文件下载时避免打印 Body,防止 OOM


三、源码原理类(进阶)

Q6:OkHttp 的请求流程是怎样的?从 enqueue 到拿到 Response 经历了什么?

答:

plain

复制代码
1. OkHttpClient.newCall(request) → 创建 RealCall
2. RealCall.enqueue() → 交给 Dispatcher 调度
3. Dispatcher 将任务放入线程池执行
4. 进入拦截器链(Chain of Responsibility 责任链模式):
   
   ① RetryAndFollowUpInterceptor
      - 处理重试(连接失败、超时等)
      - 处理重定向(3xx 响应)
   
   ② BridgeInterceptor
      - 补全请求头(Content-Type、Content-Length、Host、Cookie 等)
      - 处理 GZIP 解压
   
   ③ CacheInterceptor
      - 根据缓存策略决定是否使用缓存
      - 处理 304 Not Modified
   
   ④ ConnectInterceptor
      - 从 ConnectionPool 获取或创建连接
      - 建立 TCP + TLS 握手
   
   ⑤ 网络拦截器(如果有)
   
   ⑥ CallServerInterceptor
      - 真正向服务器写入请求数据
      - 读取响应数据

Q7:OkHttp 的连接池(ConnectionPool)是如何工作的?

答:

  • 默认配置:最大空闲连接数 5,保活时间 5 分钟

  • 复用逻辑 :通过 Route(IP + Port + Proxy)匹配,相同 Route 的连接可以复用

  • HTTP/2 多路复用:一个连接可以同时承载多个请求(Stream)

  • 清理机制:使用后台线程定期清理过期连接

kotlin

复制代码
// 源码关键逻辑
class ConnectionPool(
    maxIdleConnections: Int = 5,
    keepAliveDuration: Long = 5,
    timeUnit: TimeUnit = TimeUnit.MINUTES
)

延伸:HTTP/1.1 是串行复用(一个连接一次一个请求),HTTP/2 是多路复用(一个连接多个请求并行)。


Q8:OkHttp 的 Dispatcher 是如何管理并发请求的?

答:

Dispatcher 内部维护三个队列:

kotlin

复制代码
class Dispatcher {
    // 最大并发请求数(默认 64)
    var maxRequests = 64
    // 每个 Host 最大并发数(默认 5)
    var maxRequestsPerHost = 5
    
    // 等待执行的异步请求队列
    private val readyAsyncCalls = ArrayDeque<AsyncCall>()
    // 正在执行的异步请求队列
    private val runningAsyncCalls = ArrayDeque<AsyncCall>()
    // 正在执行的同步请求队列
    private val runningSyncCalls = ArrayDeque<RealCall>()
}

调度规则:

  • 异步请求超过 maxRequests 或同一 Host 超过 maxRequestsPerHost 时,放入 readyAsyncCalls 等待

  • 有请求完成时,从等待队列取出补充到执行队列

  • 同步请求无并发限制,只记录


Q9:OkHttp 的缓存机制是如何实现的?

答:

  • 基于 HTTP 缓存协议(Cache-ControlExpiresETagLast-Modified

  • 通过 CacheInterceptor 实现

  • 默认使用磁盘缓存(LRU 策略)

缓存策略判定:

plain

复制代码
1. 网络不可用 + 有缓存 → 使用缓存
2. Cache-Control: no-cache / no-store → 不使用缓存
3. 缓存未过期 → 直接返回缓存
4. 缓存过期但有 ETag/Last-Modified → 发送条件请求(If-None-Match)
5. 服务器返回 304 → 更新缓存时间,返回缓存内容

Q10:OkHttp 用到了哪些设计模式?

答:

设计模式 应用位置
Builder 模式 OkHttpClient.BuilderRequest.Builder
责任链模式 Interceptor.Chain --- 拦截器链依次处理
工厂模式 Call.Factory
单例模式 OkHttpClient 建议全局单例
策略模式 ConnectionSpec(TLS 配置策略)

四、实战与优化类

Q11:OkHttpClient 为什么要使用单例?

答:

  • 每个 OkHttpClient 实例都有自己的 连接池线程池

  • 创建多个实例会导致:

    • 连接无法复用(每个池独立)

    • 线程资源浪费(多个 Dispatcher 线程池)

    • 缓存不共享

  • 正确做法 :全局持有一个 OkHttpClient 实例,通过 newBuilder() 创建副本进行局部定制

kotlin

复制代码
// 复用基础配置,局部修改
val newClient = baseClient.newBuilder()
    .readTimeout(60, TimeUnit.SECONDS)  // 仅修改超时
    .build()

Q12:如何处理 OkHttp 的内存泄漏?

答:

常见泄漏场景:

  1. ResponseBody 未关闭response.body?.string() 后未 close()

  2. Callback 持有 Activity 引用:异步回调中使用匿名内部类持有外部引用

  3. 取消请求不及时:Activity 销毁时未取消未完成的请求

解决方案:

kotlin

复制代码
// 1. 使用 use 自动关闭
client.newCall(request).execute().use { response ->
    // 自动关闭
}

// 2. Activity 销毁时取消请求
override fun onDestroy() {
    super.onDestroy()
    client.dispatcher.cancelAll()  // 或取消特定 Tag 的请求
}

// 3. 使用弱引用或 ViewModel 管理生命周期

Q13:如何实现请求的统一 Token 刷新?

答:

使用 应用拦截器 拦截 401 响应,自动刷新 Token 并重试:

kotlin

复制代码
class TokenInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val response = chain.proceed(addToken(request))
        
        if (response.code == 401) {
            // 同步刷新 Token
            val newToken = refreshTokenSync()
            
            // 使用新 Token 重试
            response.close()
            return chain.proceed(addToken(request, newToken))
        }
        
        return response
    }
}

注意:刷新 Token 需要加锁防止并发刷新多次。


Q14:OkHttp 支持哪些超时配置?分别代表什么?

答:

kotlin

复制代码
OkHttpClient.Builder()
    .connectTimeout(10, TimeUnit.SECONDS)   // TCP 握手建立连接的超时
    .readTimeout(30, TimeUnit.SECONDS)      // 读取服务器响应数据的超时
    .writeTimeout(30, TimeUnit.SECONDS)     // 向服务器写入数据的超时
    .callTimeout(60, TimeUnit.SECONDS)      // 整个请求(含重试)的总超时
    .build()

Q15:OkHttp 与 Retrofit 的关系是什么?

答:

  • OkHttp:底层 HTTP 客户端,负责网络 I/O、连接管理、协议处理

  • Retrofit:基于 OkHttp 的上层封装,提供:

    • 接口注解定义 API(@GET@POST

    • 自动数据转换(Gson、Moshi 等)

    • 适配器模式支持 RxJava、Coroutines 等

关系 :Retrofit 的 create() 方法通过动态代理生成接口实现,最终调用 OkHttp 执行实际网络请求。


五、加分项(源码深度)

Q16:OkHttp 基于什么做 I/O?为什么高效?

答:

基于 Okio(Square 开源的 I/O 库),相比 Java 原生 IO:

  • Segment 机制:数据以 8KB 的 Segment 为单位传输,减少内存拷贝

  • Buffer 共享:多个 Buffer 可以共享同一个 Segment

  • 超时封装:统一的超时检测机制


Q17:HTTP/2 在 OkHttp 中是如何实现的?

答:

  • OkHttp 使用 Http2Connection 管理 HTTP/2 连接

  • 通过 OkHttpClient.protocols 配置支持的协议

  • 自动协商:TLS 握手时通过 ALPN(Application-Layer Protocol Negotiation)协商使用 HTTP/2 还是 HTTP/1.1


总结

难度 重点问题
⭐ 基础 核心类、同步异步区别、为什么用单例
⭐⭐ 进阶 拦截器区别、请求流程、连接池原理、Dispatcher 调度
⭐⭐⭐ 深入 缓存机制、设计模式、Okio 原理、HTTP/2 实现、Token 刷新机制

建议结合源码阅读 RealCall.ktRetryAndFollowUpInterceptor.ktConnectionPool.kt 等核心类,能够画出请求流程图会非常有说服力。

相关推荐
小孔龙2 小时前
RenderNode 与 DisplayList:Android 硬件加速的绘制记录与复用
android·性能优化
qizayaoshuap2 小时前
鸿蒙 ArkTS 实战:搜索列表 城市实时过滤(示例 94)
android·华为·harmonyos
朝星2 小时前
Framework筑基之Handler消息机制5(应用层)
android
2601_965798473 小时前
ForumLab Review: Fast PHP Script for SEO Forum Sites
android·adb·php
LUCKY-LIVING3 小时前
ELF File in linux
android·java·linux
xqqxqxxq3 小时前
SQL 字段约束笔记
android·笔记·sql
AZaLEan__3 小时前
原生XMLHttpRequest + axios + fetch
okhttp
深念Y3 小时前
技术探索记录 在 Android 手机上运行 One API
android·linux·服务器·智能手机·go·交叉编译·服务
summerkissyou198713 小时前
Android - 摄像头 - hal - 开发教程,例子,常见问题,分析方法,解决方案
android