OkHttp 拦截器机制:Application 与 Network 拦截器的区别
OkHttp 的拦截器,说白了就是请求和响应的“中间人”。每次你发送请求,它都会经过一条拦截器链。你可以插进去做一些自己的事,比如打日志、加头、重试、甚至修改响应体。我个人觉得,这是 OkHttp 最灵活的设计之一。
不过刚开始搞的时候,我也迷糊过——为什么有两个拦截器类型?不都是拦截器吗?后来在项目里踩了几个坑才算真正明白。今天就聊聊 Application Interceptor 和 Network Interceptor 到底差在哪,以及怎么写一个靠谱的自定义拦截器。
两种拦截器:位置与触发时机
| 维度 | Application Interceptor | Network Interceptor |
|---|---|---|
| 注册方式 | addInterceptor() |
addNetworkInterceptor() |
| 触发次数 | 1 次(无论是否重定向/重试) | 每次网络请求都触发(包括重定向、重试) |
| 可访问数据 | 只能看到原始请求/最终响应,不感知中间步骤 | 可以看到真实的网络请求/响应(如压缩、重定向链) |
是否能看到 GZip 解压后的内容 |
是(OkHttp 自动解压后你才拿到) | 否(拿到的是压缩前的原始字节) |
| 常用场景 | 全局日志、统一 header | 请求重试、重定向跟踪、条件缓存 |
为什么会这样?因为 Application Interceptor 在拦截器链的最外层,离你的代码最近。不管你重试多少次、重定向几次,它只触发一次。而 Network Interceptor 在链的最里层,每次真正的网络 I/O 都会经过它。我有个朋友曾把重试逻辑写在 Application Interceptor 里,结果发现重试后只打了一次日志——嗯,就是这个问题。
自定义拦截器:写一条自己的“中间人”
写一个自定义拦截器其实不复杂。你只需要实现 Interceptor 接口,重写 intercept(Chain chain) 方法。里面必须调用 chain.proceed(request) 才能继续往下走,否则请求就断了。
我见过新手直接 return 一个自己造的 Response,结果后面的拦截器全不执行。记住:不调用 proceed,链就断了。
下面是一个简单的日志拦截器,帮我排查了不少线上问题:
public class LoggingInterceptor implements Interceptor {
private static final String TAG = "OkHttp";
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
long t1 = System.nanoTime();
System.out.println(TAG + " → " + request.method() + " " + request.url());
Response response = chain.proceed(request);
long t2 = System.nanoTime();
System.out.println(TAG + " ← " + response.code() + " " + response.request().url()
+ " (" + (t2 - t1) / 1e6d + "ms)");
return response;
}
}
使用时直接用 new OkHttpClient.Builder().addInterceptor(new LoggingInterceptor()).build() 就行。如果你希望看到每次重定向的真实请求,就换成 addNetworkInterceptor。
response.body().string() 后不处理就返回。因为 body 是流式读取,读一次就没了。如果你在拦截器里读了,后面的代码拿到的 body 就是空的。我曾经犯过这个错,排查了半天——每次请求返回的数据都是空,页面一直白屏。
拦截器链的完整流程
把两种拦截器画在一张图中,就清楚了。下面这张 SVG 展示了一个请求从你的代码出发,经过 Application 拦截器的外层,再到 Network 拦截器的内层,最后到达服务器的过程。
实战中的经验总结
- 选择注册方式要谨慎:如果你要打日志并且希望每次真实请求都记录(包括重定向),用
addNetworkInterceptor;如果你只想记录一次,用addInterceptor。 - 不要阻塞主线程:拦截器默认运行在 OkHttp 的 IO 线程池里,但如果你在里面做了耗时操作(比如数据库读写),会影响整个请求池。我建议只做轻量操作。
- 抛异常要小心:如果你的拦截器抛了
IOException,OkHttp 会直接断开请求链,不会尝试重试(除非你配了重试策略)。我曾经在拦截器里做网络判断,没加超时控制,导致生产环境请求大面积超时——教训深刻。
嗯,关于拦截器的核心区别和自定义方法,今天就聊到这儿。你想想看,无论是做 Mock 测试、还是 CI 里的网络回放,拦截器都是最重要的钩子。理解了这两种拦截器,后面我们搞 Mock 拦截器就会顺手很多。