拦截器概述与作用:拦截器在OkHttp中的角色、拦截器分类
在 OkHttp 里,拦截器(Interceptor)是个非常核心的机制。简单说,它就是一条管道上的“检查站”或“加工厂”。每一个 HTTP 请求在发出前、收到响应后,都会经过这些拦截器。
我自己刚接触 OkHttp 时,还以为拦截器只是个高级的“回调接口”,后来踩了几个坑才明白——它的本质是一个 拦截器链(Interceptor Chain)。每个拦截器都可以对 Request 做修改,或对 Response 做处理,甚至自行终止请求。说白了,这就是 OkHttp 留给开发者做自定义网络操作的最佳切入点。
拦截器在 OkHttp 中的角色
- 修改请求:比如统一添加 Header(Authorization、User-Agent)、对请求体加密、记录日志。
- 修改响应:比如统一解析错误码、解压 Gzip、缓存命中后的响应改写。
- 短路请求:如果缓存策略允许,拦截器可以直接返回一个缓存的 Response,不再发起网络请求。
- 重试与重定向:比如在 401 时自动刷新 并重试。
- 统计与监控:统计请求耗时、成功率、流量大小等。
我记得有一个项目,后端接口突然改了日期格式,导致客户端解析崩溃。我直接在应用拦截器里加了一段逻辑,把响应体里的日期字符串统一替换掉——不用等后端发版,线上问题立刻修复。这就是拦截器的威力。
拦截器分类:应用拦截器 vs 网络拦截器
OkHttp 提供两种拦截器:addInterceptor() 添加的是应用拦截器(Application Interceptor),addNetworkInterceptor() 添加的是网络拦截器(Network Interceptor)。区别在哪?我来细说。
| 对比维度 | 应用拦截器 | 网络拦截器 |
|---|---|---|
| 添加方式 | addInterceptor() |
addNetworkInterceptor() |
| 执行时机 | 在请求缓存命中之前、重试/重定向之前 | 在真正发起网络请求之前、收到网络响应之后 |
| 是否受重定向影响 | 不受(只看到一次请求 / 响应) | 受影响(每次重定向都会执行) |
| 是否受缓存影响 | 不受(即使缓存命中也会执行) | 受缓存影响(如果缓存直接返回,网络拦截器不会执行) |
| 典型应用 | 日志打印、Header 统一添加、错误码统一处理、缓存策略 | Gzip 解压、自定义协议、流量监控、SSL Pinning |
举个例子。我曾经需要在 App 里统一打印请求和响应日志。一开始用了网络拦截器,结果发现每次重定向都会打三遍日志,而且缓存命中时日志干脆不打了——排查问题非常痛苦。后来换成了应用拦截器,日志干净、一致,不管重定向还是缓存都只打一次。你看,选对分类能省很多事。
核心执行顺序
理解执行顺序是正确使用拦截器的前提。我画个简易时序:
- Client 发起请求 → 遍历所有应用拦截器(按添加顺序执行)
- 应用拦截器可以修改请求、甚至短路返回缓存响应
- 经过全部应用拦截器后,进入 OkHttp 内核(处理缓存、重试、重定向)
- 如果决定真正发起网络请求 → 遍历所有网络拦截器(按添加顺序)
- 网络拦截器执行真正的 I/O 请求,拿到原始响应
- 响应原路返回:网络拦截器链(逆序)→ 应用拦截器链(逆序)→ Client
如何选择:实战建议
这里没有银弹。我根据自己的项目经验总结了几个场景,直接套用即可:
- 需要读取请求头/响应头(例如 过期自动刷新):用应用拦截器,因为它能捕获重定向前的原始响应。
- 需要真正网络流量数据(例如监控实际传输字节):用网络拦截器,因为它拿到的 Response 就是原始网络返回的。
- 统一错误码处理:用应用拦截器,避免重定向时重复处理。
- 自定义连接协议(例如模拟表单上传进度):用网络拦截器,因为它能访问到真实的
RequestBody和ResponseBody。 - 缓存策略辅助:用应用拦截器,因为它不受 OkHttp 默认缓存机制的影响,你可以自己决定是否返回缓存。
嗯,最后补充个细节。当你在应用拦截器里调用 chain.proceed(request) 之后,返回的 Response 可能来自缓存,也可能来自网络。而在网络拦截器里调用 chain.proceed(request) 之后,拿到的 Response 一定 是真实网络返回的(除非你手动短路)。这个差异在设计缓存协同优化时至关重要,我们后面的章节会重点展开。