
在 OkHttp 拦截器中,绝不可主动调用 response.body().close(),否则将导致下游调用方无法读取响应体,引发 IOException 或空响应等不可预期错误。资源关闭应由最终消费者(如 Retrofit、手动 response.body().string() 调用者)负责。
在 okhttp 拦截器中,**绝不可主动调用 `response.body().close()`**,否则将导致下游调用方无法读取响应体,引发 `ioexception` 或空响应等不可预期错误。资源关闭应由最终消费者(如 retrofit、手动 `response.body().string()` 调用者)负责。
OkHttp 的拦截器链(Interceptor Chain)是责任链模式的典型实现:每个拦截器接收上游传入的 Response,可选择修改、重试或直接返回,但不承担释放响应资源的责任。Response 及其 ResponseBody 的生命周期由发起请求的最外层代码控制——例如,当你调用 response.body().string()、response.body().bytes() 或 response.body().source().readAll() 时,OkHttp 内部会在读取完成后自动关闭底层流;若使用 response.body().source() 手动读取,则需确保在读取完毕后显式调用 response.body().close()。
回到你的重试拦截器示例:
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
if (!response.isSuccessful() && response.code() == 429) {
LOG.error("Rate limit exceeded. Waiting 20 seconds.");
try {
Thread.sleep(20_000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // ✅ 正确处理中断
throw new IOException("Request interrupted during 429 backoff", e);
}
// ⚠️ 注意:此处原 response 已被 consume 或即将被丢弃,
// 但绝不在此 close!由后续逻辑决定是否及如何消费新 response。
response.close(); // ❌ 错误:可能已部分读取,且干扰链路
response = chain.proceed(chain.request()); // ✅ 重新发起请求
}
return response; // ✅ 正确:让调用方决定如何处理和关闭
}⚠️ 特别注意以下几点:
- 不要在拦截器中调用 response.close() 或 response.body().close():这会提前终止响应流,导致上层代码调用 response.body().string() 时抛出 IOException: closed。
- 重试时应确保原始 response 不被意外消费:chain.proceed() 返回的 Response 在未读取前是安全的;但一旦调用了 response.body().string() 等方法,其 body 即被消耗,再次读取将失败——因此重试逻辑中无需(也不应)手动关闭它,只需丢弃引用即可。
- 中断处理要规范:Thread.sleep() 被中断后,应恢复中断状态(Thread.currentThread().interrupt()),而非仅打印堆栈,以保障线程协作语义。
- 超时与重试策略建议解耦:生产环境推荐使用 OkHttpClient.Builder.retryOnConnectionFailure(false) 配合自定义拦截器,并引入指数退避(如 ExponentialBackoff)和最大重试次数限制,避免雪崩。
✅ 正确的资源管理原则总结:
谁消费,谁关闭 —— 只有最终读取响应体的代码(如业务层解析 JSON、日志记录响应内容)才应负责调用 response.body().close();拦截器只负责逻辑编排,不干预资源生命周期。
遵循该原则,既能消除 “response not closed” 警告,又能保证响应数据完整、线程安全、可维护性强。









