OkHttp拦截器机制:Interceptor链与自定义拦截器

说到OkHttp,我觉得最巧妙的设计就是它的拦截器机制了。说白了,整个网络请求的处理流程,就是一条拦截器链。每个拦截器各司其职,像流水线一样把请求包装、处理,再把响应拆解、返回。

我记得刚接触OkHttp源码时,看到这一块儿的设计,确实有种恍然大悟的感觉。你想想看,一个网络库要处理重定向、缓存、连接复用那么多事儿,如果全写在一个大方法里,那代码得多难看?用拦截器链来解耦,思路很清晰。

核心思想:OkHttp将请求处理过程拆解为多个独立阶段,每个阶段由一个Interceptor负责。它们首尾相连,形成一条责任链(Chain of Responsibility)。

1. Interceptor链:结构和工作原理

Interceptor链是怎样的结构呢?我画了一张图,你一看就明白。

OkHttp 拦截器链结构 请求发起方向: 请求 响应返回方向: 响应 应用拦截器 (自定义) RetryAndFollow UpInterceptor BridgeInterceptor (桥接) CacheInterceptor (缓存) ConnectInterceptor (连接) 网络拦截器 (自定义) CallServerInterceptor (实际网络I/O) 关键说明: 1. 请求从左边进入,依次经过每个拦截器,到达最右边的实际网络。 2. 响应从右边返回,逆向经过所有拦截器。 3. 应用拦截器 (App Interceptor) 在 RetryAndFollowUp 之前执行。 4. 网络拦截器 (Network Interceptor) 在 ConnectInterceptor 之后执行。

每个Interceptor里都有一个intercept()方法。它会拿到当前的Chain对象,调用chain.proceed()把请求传给下一环。嗯,这里要注意,proceed()返回的是Response。所以拦截器既可以在请求发出前做手脚,也可以在响应返回后做处理。

2. 五大内置拦截器

OkHttp默认内置了5个拦截器。它们的执行顺序是固定的,每个都有不可替代的作用。

顺序 拦截器 职责
1 RetryAndFollowUpInterceptor 自动重试、跟随重定向
2 BridgeInterceptor 组装请求头(如Content-Length),解压Gzip响应
3 CacheInterceptor 处理缓存策略(ETag、If-Modified-Since)
4 ConnectInterceptor 建立与服务器的连接(HTTP/HTTPS)
5 CallServerInterceptor 执行网络I/O,发送请求并读取响应

小提示:CacheInterceptor之前的所有拦截器,在缓存命中时可能根本不会继续往下传请求。这就是它能省掉网络请求的原因。

3. 添加自定义拦截器

在实际项目中,我们经常要加自己的拦截器。比如我做过一个日志拦截器,用来打网络请求日志。也做过一个过期自动刷新的拦截器。

给OkHttp加拦截器有两种姿势:应用拦截器和网络拦截器。区别在哪呢?

  • 应用拦截器(Application Interceptor):用addInterceptor()添加。它在RetryAndFollowUpInterceptor之前执行。也就是说,无论重试多少次,它只执行一次。适合加请求头、做统一日志。
  • 网络拦截器(Network Interceptor):用addNetworkInterceptor()添加。它在ConnectInterceptor之后执行。每次网络请求都会触发。如果请求重试了3次,网络拦截器也会执行3次。适合监控实际网络流量。

写一个自定义拦截器,其实很简单。你只需要实现Interceptor接口,覆盖intercept()方法即可。


class LoggingInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val startTime = System.nanoTime()

        // 请求前打印
        println("---> ${request.method} ${request.url}")

        try {
            val response = chain.proceed(request)
            val duration = (System.nanoTime() - startTime) / 1_000_000

            // 响应后打印
            println("<--- ${response.code} ${response.message} (${duration}ms)")
            return response
        } catch (e: Exception) {
            println("<--- FAILED: ${e.message}")
            throw e
        }
    }
}

然后把它加到OkHttpClient上:


val client = OkHttpClient.Builder()
    .addInterceptor(LoggingInterceptor())  // 应用拦截器
    .addNetworkInterceptor(LoggingInterceptor()) // 网络拦截器
    .build()

避坑指南:我曾经踩过一个坑——在应用拦截器里对响应体做了一次string()读取。结果下游拿到Response后,调用response.body?.string()永远返回空!因为Response Body只能读一次。解决方案是使用BufferedSource或提前拷贝。

4. 拦截器执行顺序验证

为了让你更直观地感受一下,我列一个实际案例。假设你添加了1个应用拦截器(App)和1个网络拦截器(Net),然后请求经历了一次重定向(302)。

那么拦截器的调用顺序是这样的:

  1. App Interceptor(只有一次)
  2. RetryAndFollowUpInterceptor(发现302,发起第二次请求)
  3. BridgeInterceptor
  4. CacheInterceptor(未缓存)
  5. ConnectInterceptor
  6. Net Interceptor(第一次网络请求)
  7. CallServerInterceptor(发送第一个请求,得到302)
  8. 响应返回,经过Net Interceptor -> ... -> RetryAndFollowUp 发现要重定向
  9. RetryAndFollowUpInterceptor构造新请求,再次走一遍链
  10. ... 再次到达ConnectInterceptor
  11. Net Interceptor(第二次网络请求)
  12. CallServerInterceptor(发送重定向后的请求,得到200)
  13. 最终响应返回App Interceptor

结论:应用拦截器只执行了1次,网络拦截器执行了2次。这就是你选择用哪种拦截器的依据。

5. 实际应用场景

我个人建议,绝大多数场景下,用应用拦截器就够了。比如:

  • 添加公共请求头(Authorization、User-Agent等)
  • 统一打印请求/响应日志
  • 自动刷新处理
  • 网络异常时的兜底策略

而网络拦截器,适合做这些:

  • 监控真实的网络流量(比如抓包)
  • 统计每次网络请求的耗时和字节数
  • 模拟弱网环境(通过拦截器延迟或返回错误)

好了,关于OkHttp的拦截器机制,就说这么多。记住一句话:理解拦截器链,你就掌握了OkHttp的精髓。以后遇到任何网络请求的定制需求,第一反应就是写个拦截器加上去。