拦截器的作用与分类
1. 拦截器到底干了啥?
拦截器是 OkHttp 的“管道工”。请求从发起到返回,中间经过一道一道的拦截器。每个拦截器都可以看一眼请求、改一改请求、记录一下日志、甚至直接返回一个假的响应。我个人习惯把拦截器想象成“链式守卫”——每过一个关卡,你可以塞点私货进去。
举个例子,在项目中我需要统一添加 Authorization 请求头。如果没拦截器,你得在每个请求的构建代码里手动加,能加到你崩溃。有了拦截器,一行代码全搞定。这就是拦截器的核心价值:拦截与修改。
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(chain -> {
Request request = chain.request();
Request newRequest = request.newBuilder()
.header("Authorization", "Bearer my_")
.build();
return chain.proceed(newRequest);
})
.build();
2. 两种拦截器:Application Interceptor vs Network Interceptor
OkHttp 里有两种拦截器,很多人刚接触时傻傻分不清:addInterceptor() 和 addNetworkInterceptor()。说白了,它们被调用的时机不同,用途也不同。
Application Interceptor(应用拦截器)
它位于最外层,只调用一次。即使发生了重定向或重试,它也只看到最初的请求和最终的响应。我在项目中常在应用拦截器里做统一加解密、打印请求日志——因为不看内部细节,只看业务结果。
Network Interceptor(网络拦截器)
它更贴近网络层,每一次真正的网络请求(包括重定向、重试)都会经过它。如果你想看真实的中间跳转、网络耗时、Cookie 变化,用网络拦截器更合适。我记得有一次排查请求超时问题,就是在网络拦截器里打印了每个重定向的 URL,才发现是某个跳转地址格式不对。
| 对比维度 | Application Interceptor | Network Interceptor |
|---|---|---|
| 调用次数 | 只 1 次(不管重定向) | 可能多次(每次网络请求都调用) |
| 可访问的响应 | 最终响应(已处理重定向) | 原始响应(含中间重定向) |
| 典型用途 | 统一 header、日志、异常处理 | 重定向追踪、网络缓存、Cookie 注入 |
| 添加方法 | addInterceptor() |
addNetworkInterceptor() |
3. 添加顺序与优先级
说到顺序,这里有个容易踩的坑。拦截器是按添加顺序执行的——越早添加的越先生效。但这只是表面,实际执行链更像一个洋葱结构:请求进去时先过应用拦截器,再过网络拦截器;响应回来时反着来,先过网络拦截器,再过应用拦截器。
为什么顺序这么关键?举个例子:我在一个项目里先加了网络拦截器用于打网络日志,后加了应用拦截器用于统一加 。结果发现 没被加到重定向后的请求里——因为应用拦截器只跑一次,而网络拦截器每次都跑,重定向时 早被处理掉了。这坑我踩过一次,好几天才想明白。
优先级其实就一句话:添加顺序决定了内外层关系。如果你想让一个拦截器总是最后处理请求(比如加签名),就最后添加;如果想让一个拦截器最底层的网络行为(比如重试),就最先添加。
// 举个例子:
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor()) // 应用层,第1个添加 → 最外层
.addInterceptor(new AuthInterceptor()) // 应用层
.addNetworkInterceptor(new RetryInterceptor()) // 网络层
.addNetworkInterceptor(new CookieInterceptor()) // 网络层
.build();
上面这段代码的执行顺序是:请求 → Logging → Auth → (进入网络层) → Retry → Cookie → 真正的网络请求 → Cookie → Retry → (回到应用层) → Auth → Logging → 返回给调用方。
4. 实战中的避坑指南
- 不要在网络拦截器中修改请求方法/路径:如果重定向后的请求重新生成,可能导致循环重定向。我在项目里试过把 GET 改成 POST,结果无限 302 崩溃。
- 应用拦截器里尽量不抛异常:如果抛出运行时异常,OkHttp 不会进行重试,整个请求链直接断。我建议用
catch捕获并返回一个自定义错误响应。 - 网络拦截器里慎用
close():响应体一旦关闭,上层就再也读不到了。我有次在调试时提前关闭了 Response body,结果应用拦截器拿到的数据为空,找了半天 bug。