Why does OkHttp step-by-step response. body (). string () can only be called once,
I think you have used or touched OkHttp. Recently, when I used Okhttp, I stepped on a trap and shared it here. In the future, when you encounter similar problems, you can go around.
It is not enough to solve the problem. This article will focus on analyzing the root cause of the problem from the source code point of view.
1. Problems Found
During development, I initiate a request by constructing an OkHttpClient object and add it to the queue. After the server responds, the Callback interface triggers the onResponse () method, in this method, the Response object is used to process the returned results and implement the business logic. The code is roughly as follows:
// Note: unrelated code getHttpClient () is deleted to focus on the problem (). newCall (request ). enqueue (new Callback () {@ Override public void onFailure (Call call, IOException e) {}@ Override public void onResponse (Call call, Response response) throws IOException {if (BuildConfig. DEBUG) {Log. d (TAG, "onResponse:" + response. body (). toString ();} // parse the Request body parseResponseStr (response. body (). string ());}});
In onResponse (), to facilitate debugging, I printed the response body and parsed the response body using the parseResponseStr () method (Note: response is called twice. body (). string ()).
This Code does not seem to have any problems, but the problem occurs after the actual operation: You can see that the returned body data (json) is successfully printed on the console, but then an exception is thrown:
java.lang.IllegalStateException: closed
2. Solve the Problem
After checking the code, it is found that when parseResponseStr () is called, response. body (). string () is used again as the parameter. Due to the rush of time, I found that response. body (). string () can only be called once after Internet access, so I fixed the problem after modifying the logic in the onResponse () method:
GetHttpClient (). newCall (request ). enqueue (new Callback () {@ Override public void onFailure (Call call, IOException e) {}@ Override public void onResponse (Call call, Response response) throws IOException {// here, first save the response body to the memory String responseStr = response. body (). string (); if (BuildConfig. DEBUG) {Log. d (TAG, "onResponse:" + responseStr);} // parse the Request body parseReponseStr (responseStr );}});
3. Combined with source code analysis
After the problem is solved, we still need to analyze it later. The previous knowledge of OkHttp was limited to usage, and I did not carefully analyze the internal implementation details. I took the time to look down over the weekend and figured out the cause of the problem.
First, analyze the most intuitive question: why can only response. body (). string () be called once?
In terms of splitting, first use response. body () to get the ResponseBody object (which is an abstract class, here we do not need to care about the specific implementation class), and then call the string () method of ResponseBody to get the content of the response body.
The body () method is correct after analysis. Let's look at the string () method:
public final String string() throws IOException { return new String(bytes(), charset().name());}
It is easy to convert the byte [] array returned by the byte () method into a String object by specifying the character set (charset). There is no problem in the Construction. continue to look at the byte () method:
Public final byte [] bytes () throws IOException {//... bufferedSource source = source (); byte [] bytes; try {bytes = source. readByteArray ();} finally {Util. closeQuietly (source );}//... return bytes ;}//... indicates that the irrelevant code is deleted, the same below.
In the byte () method, the byte [] array is read and returned through the BufferedSource interface object. Combined with the exception mentioned above, I noticed the Util. closeQuietly () method in the finally code block. Excuse me? Silently close ???
This method looks very strange. Let's check it out:
public static void closeQuietly(Closeable closeable) { if (closeable != null) { try { closeable.close(); } catch (RuntimeException rethrown) { throw rethrown; } catch (Exception ignored) { } }}
It turns out that the BufferedSource interface mentioned above can be understood as a resource Buffer Based on the comments in the code document. It implements the Closeable interface and closes and releases resources by rewriting the close () method. Next, let's look at what the close () method has done (in the current scenario, the BufferedSource implementation class is RealBufferedSource ):
// Hold the Source object public final Source source; @ Overridepublic void close () throws IOException {if (closed) return; closed = true; source. close (); buffer. clear ();}
Obviously, close and release resources through source. close. Here, the function of the closeQuietly () method is self-evident, that is, to close the BufferedSource interface object held by the ResponseBody subclass.
At this point, we suddenly realized that when we called response for the first time. body (). when string () is returned, OkHttp calls the closeQuietly () method to silently release the resource while returning the buffer resource of the response body.
In this way, when we call the string () method again, we still return to the above byte () method. This problem occurs in the line of code bytes = source. readByteArray. Let's take a look at the readByteArray () method of RealBufferedSource:
@Overridepublic byte[] readByteArray() throws IOException { buffer.writeAll(source); return buffer.readByteArray();}
Continue to read the writeAll () method:
@Overridepublic long writeAll(Source source) throws IOException { //... long totalBytesRead = 0; for (long readCount; (readCount = source.read(this, Segment.SIZE)) != -1; ) { totalBytesRead += readCount; } return totalBytesRead;}
The problem lies in the source. read () of the for loop. Remember that when the close () method is analyzed above, it calls source. close () to close and release resources. So what will happen when you call the read () method again:
@Overridepublic long read(Buffer sink, long byteCount) throws IOException { //... if (closed) throw new IllegalStateException("closed"); //... return buffer.read(sink, toRead);}
So far, it is against the crash I encountered earlier:
java.lang.IllegalStateException: closed
4. Why is OkHttp designed like this?
Through fuc * ing the source code, we found the root of the problem, but I still have a question: why is OkHttp designed like this?
In fact, the best way to understand this problem is to view the ResponseBody comments, as JakeWharton replied in issues:
reply of JakeWharton in okhttp issues
In a simple sentence: It's got ented on ResponseBody. So I ran to see the class comment document and finally sorted It out as follows:
In actual development, the resources held by the response body RessponseBody may be very large, so OkHttp does not directly save it to the memory, but only holds data stream connections. Data is retrieved from the server and returned only when necessary. At the same time, considering that the application is unlikely to read data repeatedly, It is designed as a one-time stream (one-shot). After reading the data, it will 'close and release resource '.
5. Summary
Finally, the following points of attention are summarized:
1. The response body can only be used once;
2. the response body must be closed: It is worth noting that in scenarios such as file downloading, when you use response. body (). when obtaining the input stream in byteStream () format, you must use Response. close () to manually close the response body.
3. to obtain the response body data, use bytes () or string () to read the entire response into the memory, or use source (), byteStream (), charStream () method to transmit data in the form of a stream.
4. The following method triggers the closure of the response body:
Response.close()Response.body().close()Response.body().source().close()Response.body().charStream().close()Response.body().byteString().close()Response.body().bytes()Response.body().string()
Summary
The above is a small Editor to tell you why the OkHttp step-by-step response. body (). string () can only be called once. I hope it will be helpful to you. If you have any questions, please leave a message and the editor will reply to you in time. Thank you very much for your support for the help House website!