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 拦截器链完整执行流程
- 构建调用:
OkHttpClient.newCall(request)创建RealCall对象。 - 启动异步/同步调用: 调用
enqueue()或execute()触发请求。 - 应用拦截器链: 按添加顺序(用户注册顺序)依次执行,每个拦截器可修改请求或响应。
- 网络拦截器链: 若应用拦截器最终调用
chain.proceed(),OkHttp 会构建网络拦截器链(按注册顺序执行),最终发起真正的 HTTP 请求。 - 响应回溯: 网络拦截器后的 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,网络拦截器再次修改时需注意顺序和覆盖逻辑。
若应用拦截器已经添加了某个 Header,网络拦截器再次修改时需注意顺序和覆盖逻辑。
2.9 性能与稳定性优化
- 避免在拦截器中执行耗时同步操作(如数据库查询、网络请求),否则会阻塞整个链的执行线程。
- 网络拦截器适合实现自动重试(如
RetryInterceptor),但需设置最大重试次数,防止无限循环。 - 利用
Chain.connectTimeoutMillis()、readTimeoutMillis()等方法可在拦截器内动态调整超时时间,增强稳定性。
2.10 关键 API 速查
| API 方法 | 所属接口/类 | 说明 |
|---|---|---|
addInterceptor(Interceptor) | OkHttpClient.Builder | 添加应用拦截器 |
addNetworkInterceptor(Interceptor) | OkHttpClient.Builder | 添加网络拦截器 |
chain.request() | Interceptor.Chain | 获取当前请求 |
chain.proceed(Request) | Interceptor.Chain | 将请求传递给下一个拦截器,获得响应 |
response.request() | Response | 获取最终产生该响应的请求(可能已重定向) |