Why can't response.body().string() be called multiple times?
I believe everyone has used or come into contact with OkHttp. When I was using Okhttp recently, I stepped on a pit. I will share it here so that everyone can bypass it when they encounter similar problems in the future.
Just a solution Problems are not enough. This article will focus on analyzing the root of the problem from the source code perspective, which is full of useful information.
1. Found the problem
#During development, I initiated a request by constructing the OkHttpClient object and added it to the queue. After the server responded, The Callback interface triggers the onResponse() method, and then uses the Response object to process the return results and implement business logic in this method. The code is roughly as follows:
//注:为聚焦问题,删除了无关代码 getHttpClient().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()); } //解析请求体 parseResponseStr(response.body().string()); } });
In onResponse(), for the convenience of debugging, I printed the return body, and then parsed the return body through the parseResponseStr() method (note: response.body() is called twice here. string() ).
This code, which seems to have no problem, actually has a problem after running: through the console, it can be seen that the return body data (json) is successfully printed, but then an exception is thrown:
java.lang.IllegalStateException: closed
2. Solve the problem
After checking the code, I found that the problem lies in calling parseResponseStr() and using response.body().string again. () as parameter. Because I was in a hurry, I checked online and found that response.body().string() can only be called once, so I modified the logic in the onResponse() method and solved the problem:
getHttpClient().newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) {} @Override public void onResponse(Call call, Response response) throws IOException { //此处,先将响应体保存到内存中 String responseStr = response.body().string(); if (BuildConfig.DEBUG) { Log.d(TAG, "onResponse: " + responseStr); } //解析请求体 parseReponseStr(responseStr); } });
3. Combined with the source code analysis problem
After the problem is solved, it still needs to be analyzed afterwards. Since my previous understanding of OkHttp was limited to its use, and I had not carefully analyzed the details of its internal implementation, I took the time to look down on it over the weekend and figured out the cause of the problem.
Let’s first analyze the most intuitive question: Why can response.body().string() only be called once?
Disassembly, first get the ResponseBody object (it is an abstract class, we don’t need to care about the specific implementation class here) through response.body(), and then call the string() method of ResponseBody to get the response body content.
After analysis, there is no problem with the body() method. Let’s look down at the string() method:
public final String string() throws IOException { return new String(bytes(), charset().name()); }
It’s very simple. The byte() method returns the byte[ by specifying the character set (charset). ] The array is converted into a String object. There is no problem with 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; } //... 表示删减了无关代码,下同。
In the byte() method, read the byte[] array through the BufferedSource interface object and return it. Combined with the exception mentioned above, I noticed the Util.closeQuietly() method in the finally code block. excuse me? Close silently? ? ?
This method looks weird. Is it right? Follow up and have a look:
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 according to the code documentation comments. Implemented the Closeable interface and closed and released resources by overriding the close() method. Then look down to see what the close() method does (in the current scenario, the BufferedSource implementation class is RealBufferedSource):
//持有的 Source 对象 public final Source source; @Override public void close() throws IOException { if (closed) return; closed = true; source.close(); buffer.clear(); }
Obviously, close and release the resource through source.close(). Speaking of which, the function of the closeQuietly() method is self-evident, which is to close the BufferedSource interface object held by the ResponseBody subclass.
Analysis at this point, we suddenly realize: when we call response.body().string() for the first time, OkHttp returns the buffer resources of the response body and calls the closeQuietly() method to silently release the resources.
In this way, when we call the string() method again, we still return to the byte() method above. This time the problem lies in the bytes = source.readByteArray() line of code. Let’s take a look at the readByteArray() method of RealBufferedSource:
@Override public byte[] readByteArray() throws IOException { buffer.writeAll(source); return buffer.readByteArray(); }
Continue to look at the writeAll() method:
@Override public 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 when analyzing the close() method above, it called source.close() to close and release the resource. So, what happens when the read() method is called again:
@Override public long read(Buffer sink, long byteCount) throws IOException { //... if (closed) throw new IllegalStateException("closed"); //... return buffer.read(sink, toRead); }
At this point, it meets the crash I encountered earlier:
java.lang.IllegalStateException: closed
4.OkHttp Why is it designed like this?
By fuc*ing the source code, we found the root of the problem, but I still have a question: Why is OkHttp designed this way?
In fact, the best way to understand this problem is to view the annotation documentation of ResponseBody, as JakeWharton responded in issues:
reply of JakeWharton in okhttp issues
In a simple sentence: It's documented on ResponseBody. So I ran to read the class annotation documentation, and finally summarized it as follows:
在实际开发中,响应主体 RessponseBody 持有的资源可能会很大,所以 OkHttp 并不会将其直接保存到内存中,只是持有数据流连接。只有当我们需要时,才会从服务器获取数据并返回。同时,考虑到应用重复读取数据的可能性很小,所以将其设计为 一次性流(one-shot) ,读取后即 '关闭并释放资源'。
5.总结
最后,总结以下几点注意事项,划重点了:
1.响应体只能被使用一次;
2.响应体必须关闭:值得注意的是,在下载文件等场景下,当你以 response.body().byteStream() 形式获取输入流时,务必通过 Response.close() 来手动关闭响应体。
3.获取响应体数据的方法:使用 bytes() 或 string() 将整个响应读入内存;或者使用 source() , byteStream() , charStream() 方法以流的形式传输数据。
4.以下方法会触发关闭响应体:
Response.close() Response.body().close() Response.body().source().close() Response.body().charStream().close() Response.body().byteString().close() Response.body().bytes() Response.body().string()
上面是我整理给大家的,希望今后会对大家有帮助。
相关文章:
The above is the detailed content of Why can't response.body().string() be called multiple times?. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

AI Hentai Generator
Generate AI Hentai for free.

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics



Convert basic data types to strings using Java's String.valueOf() function In Java development, when we need to convert basic data types to strings, a common method is to use the valueOf() function of the String class. This function can accept parameters of basic data types and return the corresponding string representation. In this article, we will explore how to use the String.valueOf() function for basic data type conversions and provide some code examples to

Method of converting char array to string: It can be achieved by assignment. Use {char a[]=" abc d\0efg ";string s=a;} syntax to let the char array directly assign a value to string, and execute the code to complete the conversion.

Replace characters (strings) in a string using Java's String.replace() function In Java, strings are immutable objects, which means that once a string object is created, its value cannot be modified. However, you may encounter situations where you need to replace certain characters or strings in a string. At this time, we can use the replace() method in Java's String class to implement string replacement. The replace() method of String class has two types:

Hello everyone, today I will share with you the basic knowledge of Java: String. Needless to say the importance of the String class, it can be said to be the most used class in our back-end development, so it is necessary to talk about it.

Use Java's String.length() function to get the length of a string. In Java programming, string is a very common data type. We often need to get the length of a string, that is, the number of characters in the string. In Java, we can use the length() function of the String class to get the length of a string. Here is a simple example code: publicclassStringLengthExample{publ

In Golang programming, byte, rune and string types are very basic and common data types. They play an important role in processing data operations such as strings and file streams. When performing these data operations, we usually need to convert them to each other, which requires mastering some conversion skills. This article will introduce the byte, rune and string type conversion techniques of Golang functions, aiming to help readers better understand these data types and be able to apply them skillfully in programming practice.

1. Understanding String1. String in JDK First, let’s take a look at the source code of the String class in the JDK. It implements many interfaces. You can see that the String class is modified by final. This means that the String class cannot be inherited and there is no subclass of String. class, so that all people using JDK use the same String class. If String is allowed to be inherited, everyone can extend String. Everyone uses different versions of String, and two different people Using the same method shows different results, which makes it impossible to develop the code. Inheritance and method overriding not only bring flexibility, but also cause many subclasses to behave differently.

The split method in String uses the split() method of String to split the String according to the incoming characters or strings and return the split array. 1. General usage When using general characters, such as @ or, as separators: Stringaddress="Shanghai@Shanghai City@Minhang District@Wuzhong Road";String[]splitAddr=address.split("@");System .out.println(splitAddr[0]+splitAddr[1]+splitAddr[2]+splitAddr[3
