OkHttp拦截器读取ResponseBody字符串触发IllegalStateException问题
我完全懂你遇到的糟心事——写了个拦截器想监控所有服务器响应,结果读取响应体字符串时总是抛出IllegalStateException: closed,直接调用response.body().string()虽然能拿到值,但没法处理响应体为null的边界情况。咱们先拆解问题根源,再给你靠谱的解决办法。
问题到底出在哪?
从你贴的堆栈跟踪能一眼看出来:HttpLoggingInterceptor在你的拦截器之后执行了(准确说,是你的拦截器调用chain.proceed(request)后,后续拦截链里的HttpLoggingInterceptor已经读取过响应体了)。
OkHttp的ResponseBody是一次性资源:只要调用过string()、bytes()这类读取方法,底层的流就会被自动关闭,后续再尝试读取必然触发closed异常。你之前的代码里,拿到的response的body已经被后续的日志拦截器读取并关闭了,自然没法再读。
正确解决方案:克隆响应体再操作
核心思路是:先把原响应体的内容完整复制出来,创建一个新的ResponseBody替换原响应中的body,这样既能读取到你要的字符串,又不会影响后续拦截器或业务代码对响应体的正常使用。具体步骤如下:
- 读取原响应体的字节数组(
bytes()方法会一次性读取所有内容,虽然会关闭原流,但我们已经拿到了完整副本); - 用拿到的字节数组和原响应体的
MediaType创建新的ResponseBody; - 基于原响应对象,构建包含新响应体的新
Response返回,确保后续流程不受影响; - 完整处理
response或responseBody为null的边界情况。
修复后的代码示例
Interceptor authorizationInterceptor = chain -> { Request request = chain.request(); Response response = chain.proceed(request); // 先处理response为空的极端情况 if (response == null) { Timber.w("Cannot get the server response as a Response object"); return response; } ResponseBody responseBody = response.body(); // 处理响应体为空的情况 if (responseBody == null) { return response; } // 复制原响应体内容到字节数组,原流会被关闭但我们已拿到副本 byte[] responseBytes = responseBody.bytes(); // 创建新的ResponseBody,保留原响应的媒体类型 ResponseBody newResponseBody = ResponseBody.create(responseBytes, responseBody.contentType()); // 读取字符串内容,这里操作的是新的响应体,不会影响后续流程 String responseBodyString = newResponseBody.string(); // 返回包含新响应体的Response,确保后续拦截器/业务代码能正常读取 return response.newBuilder() .body(newResponseBody) .build(); };
额外提醒:拦截器顺序很重要
如果你的拦截器需要读取响应体,建议把它放在日志拦截器(比如HttpLoggingInterceptor)之前执行。这样你的拦截器先读取并克隆响应体,后续的日志拦截器读取的是克隆后的新响应体,不会出现资源冲突。要是顺序反过来,日志拦截器先把原响应体读关闭了,你的拦截器拿到的就是已失效的body,连克隆都做不了。
内容的提问来源于stack exchange,提问作者Louis

