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!

JavaScript core data types are consistent in browsers and Node.js, but are handled differently from the extra types. 1) The global object is window in the browser and global in Node.js. 2) Node.js' unique Buffer object, used to process binary data. 3) There are also differences in performance and time processing, and the code needs to be adjusted according to the environment.

JavaScriptusestwotypesofcomments:single-line(//)andmulti-line(//).1)Use//forquicknotesorsingle-lineexplanations.2)Use//forlongerexplanationsorcommentingoutblocksofcode.Commentsshouldexplainthe'why',notthe'what',andbeplacedabovetherelevantcodeforclari

The main difference between Python and JavaScript is the type system and application scenarios. 1. Python uses dynamic types, suitable for scientific computing and data analysis. 2. JavaScript adopts weak types and is widely used in front-end and full-stack development. The two have their own advantages in asynchronous programming and performance optimization, and should be decided according to project requirements when choosing.

Whether to choose Python or JavaScript depends on the project type: 1) Choose Python for data science and automation tasks; 2) Choose JavaScript for front-end and full-stack development. Python is favored for its powerful library in data processing and automation, while JavaScript is indispensable for its advantages in web interaction and full-stack development.

Python and JavaScript each have their own advantages, and the choice depends on project needs and personal preferences. 1. Python is easy to learn, with concise syntax, suitable for data science and back-end development, but has a slow execution speed. 2. JavaScript is everywhere in front-end development and has strong asynchronous programming capabilities. Node.js makes it suitable for full-stack development, but the syntax may be complex and error-prone.

JavaScriptisnotbuiltonCorC ;it'saninterpretedlanguagethatrunsonenginesoftenwritteninC .1)JavaScriptwasdesignedasalightweight,interpretedlanguageforwebbrowsers.2)EnginesevolvedfromsimpleinterpreterstoJITcompilers,typicallyinC ,improvingperformance.

JavaScript can be used for front-end and back-end development. The front-end enhances the user experience through DOM operations, and the back-end handles server tasks through Node.js. 1. Front-end example: Change the content of the web page text. 2. Backend example: Create a Node.js server.

Choosing Python or JavaScript should be based on career development, learning curve and ecosystem: 1) Career development: Python is suitable for data science and back-end development, while JavaScript is suitable for front-end and full-stack development. 2) Learning curve: Python syntax is concise and suitable for beginners; JavaScript syntax is flexible. 3) Ecosystem: Python has rich scientific computing libraries, and JavaScript has a powerful front-end framework.


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

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

SAP NetWeaver Server Adapter for Eclipse
Integrate Eclipse with SAP NetWeaver application server.

MinGW - Minimalist GNU for Windows
This project is in the process of being migrated to osdn.net/projects/mingw, you can continue to follow us there. MinGW: A native Windows port of the GNU Compiler Collection (GCC), freely distributable import libraries and header files for building native Windows applications; includes extensions to the MSVC runtime to support C99 functionality. All MinGW software can run on 64-bit Windows platforms.

Zend Studio 13.0.1
Powerful PHP integrated development environment

ZendStudio 13.5.1 Mac
Powerful PHP integrated development environment

mPDF
mPDF is a PHP library that can generate PDF files from UTF-8 encoded HTML. The original author, Ian Back, wrote mPDF to output PDF files "on the fly" from his website and handle different languages. It is slower than original scripts like HTML2FPDF and produces larger files when using Unicode fonts, but supports CSS styles etc. and has a lot of enhancements. Supports almost all languages, including RTL (Arabic and Hebrew) and CJK (Chinese, Japanese and Korean). Supports nested block-level elements (such as P, DIV),
