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 里,结果发现重试后只打了一次日志——嗯,就是这个问题。

一句话总结: Application Interceptor 是“业务层”的拦截器,Network 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 拦截器的内层,最后到达服务器的过程。

OkHttp 拦截器链请求/响应流程 你的代码 Application 拦截器 (addInterceptor) Network 拦截器 (addNetworkInterceptor) 服务器 请求 请求 请求 响应 响应 响应 重定向/重试时的行为差异 Application 拦截器:只会经过一次(包括重试、重定向) Network 拦截器:每次网络请求都会经过(重定向、重试时多次触发) 拦截器能看到的响应数据 Application 拦截器:已解压(GZip 已被 OkHttp 自动解开) Network 拦截器:原始字节(未经解压,可看到 Content-Encoding 头) 自定义拦截器实战 日志 / Header 注入 / 重试 / 缓存

实战中的经验总结

  1. 选择注册方式要谨慎:如果你要打日志并且希望每次真实请求都记录(包括重定向),用 addNetworkInterceptor;如果你只想记录一次,用 addInterceptor。
  2. 不要阻塞主线程:拦截器默认运行在 OkHttp 的 IO 线程池里,但如果你在里面做了耗时操作(比如数据库读写),会影响整个请求池。我建议只做轻量操作。
  3. 抛异常要小心:如果你的拦截器抛了 IOException,OkHttp 会直接断开请求链,不会尝试重试(除非你配了重试策略)。我曾经在拦截器里做网络判断,没加超时控制,导致生产环境请求大面积超时——教训深刻。
小技巧: 调试时可以同时注册 Application 和 Network 两个拦截器,把它们的日志等级分开。比如 Application 用 INFO,Network 用 DEBUG。这样你既能看清业务最终结果,又能跟踪每个网络细节。

嗯,关于拦截器的核心区别和自定义方法,今天就聊到这儿。你想想看,无论是做 Mock 测试、还是 CI 里的网络回放,拦截器都是最重要的钩子。理解了这两种拦截器,后面我们搞 Mock 拦截器就会顺手很多。