两次访问 OkHttp 响应的 body 字符串会导致 IllegalStateException: closed

Accessing body string of an OkHttp Response twice results in IllegalStateException: closed

我通过 OkHttp 库实现我的 http 调用。一切正常,但我注意到,当我两次访问 body 作为响应字符串时,将抛出 IllegalStateException 。 也就是说,我这样做(例如):Log.d("TAG", response.body().string()) 之后我实际上想像 processResponse(response.body().string()) 这样使用该字符串。但是第二次调用会抛出消息 closed 的异常。

访问一个字符串两次怎么可能失败呢?我想处理该响应而无需添加 wrapper/dummy object 只是为了保存一些值(如 header、body、状态代码)。

响应上的string方法将读取输入(网络)流并将其转换为字符串。所以它动态地构建字符串并 returns 给你。第二次调用时,网络流已经被消耗掉了,不可用了。

您应该将 string 的结果保存到一个字符串变量中,然后根据需要多次访问它。

更新:

there is a simpler, more lightweight API available now (see GitHub issue):

String responseBodyString = response.peekBody(Long.MAX_VALUE).string();
Log.d("TAG", responseBodyString);

原答案:

有关问题的解释,请参阅

然而,如果您不能轻松地将结果传递到变量中,但仍需要访问响应主体两次,您还有另一种选择:

在读取之前克隆缓冲区。这样,原始缓冲区既不会清空也不会关闭。请参阅此片段:

ResponseBody responseBody = response.body();
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE); // request the entire body.
Buffer buffer = source.buffer();
// clone buffer before reading from it
String responseBodyString = buffer.clone().readString(Charset.forName("UTF-8"))
Log.d("TAG", responseBodyString);

Square 自己在 HttpLoggingInterceptor in project okhttp 中使用了这种方法。

顺便说一句,除了 Greg Ennis 的回答外,我还能说出有一次我在手表 window 中忘记了 response.body().string() 时发生了什么。所以在调试器下,body 正在读入手表,然后网络流被关闭。

稍微扩展 user2011622 and Greg Ennis 的答案,我创建了一个方法来帮助您创建 body 的完整克隆,允许您使用 [=14= 的每个副本] 分别地。我自己使用这种方法与 Retrofit2

/**
 * Clones a raw buffer so as not to consume the original
 * @param rawResponse the original {@link okhttp3.Response} as returned
 *                    by {@link Response#raw()}
 * @return a cloned {@link ResponseBody}
 */
private ResponseBody cloneResponseBody(okhttp3.Response rawResponse) {
    final ResponseBody responseBody = rawResponse.body();
    final Buffer bufferClone = responseBody.source().buffer().clone();
    return ResponseBody.create(responseBody.contentType(), responseBody.contentLength(), bufferClone);
}

您可以调用 response.peekBody(Long.MAX_VALUE); 在内存中缓冲整个响应并获取它的轻量级副本。浪费会少很多。 See this issue on GitHub.

ResponseBody body = response.peekBody(Long.MAX_VALUE);
String content = body.string();
//do something

此代码获取响应正文并且不会消耗缓冲区。这是在 this issue

中添加的新 api