OkHttp拦截器机制:Interceptor链与自定义拦截器
说到OkHttp,我觉得最巧妙的设计就是它的拦截器机制了。说白了,整个网络请求的处理流程,就是一条拦截器链。每个拦截器各司其职,像流水线一样把请求包装、处理,再把响应拆解、返回。
我记得刚接触OkHttp源码时,看到这一块儿的设计,确实有种恍然大悟的感觉。你想想看,一个网络库要处理重定向、缓存、连接复用那么多事儿,如果全写在一个大方法里,那代码得多难看?用拦截器链来解耦,思路很清晰。
核心思想:OkHttp将请求处理过程拆解为多个独立阶段,每个阶段由一个Interceptor负责。它们首尾相连,形成一条责任链(Chain of Responsibility)。
1. Interceptor链:结构和工作原理
Interceptor链是怎样的结构呢?我画了一张图,你一看就明白。
每个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)。
那么拦截器的调用顺序是这样的:
- App Interceptor(只有一次)
- RetryAndFollowUpInterceptor(发现302,发起第二次请求)
- BridgeInterceptor
- CacheInterceptor(未缓存)
- ConnectInterceptor
- Net Interceptor(第一次网络请求)
- CallServerInterceptor(发送第一个请求,得到302)
- 响应返回,经过Net Interceptor -> ... -> RetryAndFollowUp 发现要重定向
- RetryAndFollowUpInterceptor构造新请求,再次走一遍链
- ... 再次到达ConnectInterceptor
- Net Interceptor(第二次网络请求)
- CallServerInterceptor(发送重定向后的请求,得到200)
- 最终响应返回App Interceptor
结论:应用拦截器只执行了1次,网络拦截器执行了2次。这就是你选择用哪种拦截器的依据。
5. 实际应用场景
我个人建议,绝大多数场景下,用应用拦截器就够了。比如:
- 添加公共请求头(Authorization、User-Agent等)
- 统一打印请求/响应日志
- 自动刷新处理
- 网络异常时的兜底策略
而网络拦截器,适合做这些:
- 监控真实的网络流量(比如抓包)
- 统计每次网络请求的耗时和字节数
- 模拟弱网环境(通过拦截器延迟或返回错误)
好了,关于OkHttp的拦截器机制,就说这么多。记住一句话:理解拦截器链,你就掌握了OkHttp的精髓。以后遇到任何网络请求的定制需求,第一反应就是写个拦截器加上去。