2. OkHttp 拦截器基础:拦截器链、责任链模式、应用拦截器与网络拦截器区别

2.1 本章目标

  • 理解 OkHttp 拦截器链的执行机制
  • 掌握责任链模式在拦截器链中的应用
  • 清晰区分应用拦截器(Application Interceptor)与网络拦截器(Network Interceptor)的触发时机与适用场景
  • 能够通过代码验证两者的执行顺序与数据可访问性

2.2 责任链模式与拦截器链

OkHttp 的拦截器体系基于经典的设计模式——责任链(Chain of Responsibility)。每一个拦截器对象持有一个指向下一个拦截器的引用,请求依次经过每个拦截器,直到最终发起网络调用或返回缓存。拦截器链的核心接口是 Interceptor.Chain,其 proceed(request) 方法将请求传递给链中的下一个拦截器。

内部实现中,OkHttp 将拦截器分为两个链表:

  • 应用拦截器列表(通过 addInterceptor() 添加)
  • 网络拦截器列表(通过 addNetworkInterceptor() 添加)

在 RealCall 执行时,会先构建应用拦截器链,执行完毕后若需网络请求,再构建网络拦截器链。下图展示了完整的责任链拓扑。

关键概念:责任链模式允许每个拦截器在调用 proceed() 前后分别插入逻辑(前置处理 / 后置处理),实现跨切面编程(AOP)效果。

2.3 应用拦截器 vs 网络拦截器

对比维度应用拦截器 (Application Interceptor)网络拦截器 (Network Interceptor)
注册方式OkHttpClient.Builder.addInterceptor()OkHttpClient.Builder.addNetworkInterceptor()
执行时机在发起网络请求 前(最早)在真正建立网络连接 前(晚于应用拦截器)
触发次数每一条 HTTP 请求(含重定向)只触发 1 次每次网络交互(含重定向)都会触发,可能多次
可访问的数据原始 Request / Response(未经过重定向、重试)连接层之后的 Request / Response(含重定向后实际请求)
常见用途日志记录、请求头添加、缓存策略、统一参数注入重试机制、重定向追踪、网络层监控、连接池调优
实践提示:若需要拦截 应用层 的原始请求(如统一添加 ),优先使用应用拦截器;若需要监控实际网络 I/O(如统计重定向次数),应使用网络拦截器。

2.4 拦截器链完整执行流程

  1. 构建调用: OkHttpClient.newCall(request) 创建 RealCall 对象。
  2. 启动异步/同步调用: 调用 enqueue() 或 execute() 触发请求。
  3. 应用拦截器链: 按添加顺序(用户注册顺序)依次执行,每个拦截器可修改请求或响应。
  4. 网络拦截器链: 若应用拦截器最终调用 chain.proceed(),OkHttp 会构建网络拦截器链(按注册顺序执行),最终发起真正的 HTTP 请求。
  5. 响应回溯: 网络拦截器后的 Response 逆序返回给应用拦截器。

2.5 代码示例:同时注册两种拦截器

OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(new ApplicationInterceptor())   // 应用拦截器
    .addNetworkInterceptor(new NetworkInterceptor()) // 网络拦截器
    .build();

Request request = new Request.Builder()
    .url("https://api.example.com/data")
    .build();

client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        // 处理失败
    }
    @Override
    public void onResponse(Call call, Response response) throws IOException {
        // 处理响应
    }
});

2.6 拦截器实现示例与执行顺序验证

下面实现两个最简单的拦截器,通过日志输出验证执行顺序。

public class ApplicationInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Log.d("Interceptor", "[Application] before proceed");
        Response response = chain.proceed(request);
        Log.d("Interceptor", "[Application] after proceed");
        return response;
    }
}

public class NetworkInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Log.d("Interceptor", "[Network] before proceed");
        Response response = chain.proceed(request);
        Log.d("Interceptor", "[Network] after proceed");
        return response;
    }
}

执行日志顺序(假设无重定向):

[Application] before proceed
[Network] before proceed
—— 实际网络请求 ——
[Network] after proceed
[Application] after proceed

若有重定向,网络拦截器会再次触发(日志重复出现 [Network] 前后日志),而应用拦截器仅执行一次。

2.7 调试技巧

  • 使用日志拦截器(如 HttpLoggingInterceptor)打印完整请求/响应体,区分应用层与网络层数据差异。
  • 在拦截器中设置断点,观察 chain.proceed() 返回前后的 Request / Response 对象内容。
  • 通过 Thread.currentThread().getName() 确认执行线程(应用拦截器运行在调用线程,网络拦截器运行在 OkHttp 的线程池中)。

2.8 常见错误

错误1:在应用拦截器中依赖重定向后的 URL。
应用拦截器中的 request.url() 永远是原始 URL,重定向后的 URL 只能从网络拦截器或最终 Response 的 request().url() 获取。
错误2:网络拦截器中修改请求头但未在应用拦截器中验证。
若应用拦截器已经添加了某个 Header,网络拦截器再次修改时需注意顺序和覆盖逻辑。

2.9 性能与稳定性优化

  • 避免在拦截器中执行耗时同步操作(如数据库查询、网络请求),否则会阻塞整个链的执行线程。
  • 网络拦截器适合实现自动重试(如 RetryInterceptor),但需设置最大重试次数,防止无限循环。
  • 利用 Chain.connectTimeoutMillis()、readTimeoutMillis() 等方法可在拦截器内动态调整超时时间,增强稳定性。
OkHttp 拦截器链 — 责任链模式示意图 应用拦截器 1 应用拦截器 2 …… 网络拦截器 1 网络拦截器 2 …… 实际网络请求 chain.proceed() proceed 说明: ① 应用拦截器按注册顺序执行,任何时候只触发一次。 ② 网络拦截器每次网络交互(含重定向)都会触发,可能多次。 ③ 虚线表示责任链传递过程中的跳转或当前未完全展开的路径。

2.10 关键 API 速查

API 方法所属接口/类说明
addInterceptor(Interceptor)OkHttpClient.Builder添加应用拦截器
addNetworkInterceptor(Interceptor)OkHttpClient.Builder添加网络拦截器
chain.request()Interceptor.Chain获取当前请求
chain.proceed(Request)Interceptor.Chain将请求传递给下一个拦截器,获得响应
response.request()Response获取最终产生该响应的请求(可能已重定向)