Home Web Front-end JS Tutorial Why can't response.body().string() be called multiple times?

Why can't response.body().string() be called multiple times?

Jun 13, 2018 am 10:31 AM
string

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());
  }
});
Copy after login

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
Copy after login
Copy after login

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);
  }
});
Copy after login

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());
}
Copy after login

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;
}
//... 表示删减了无关代码,下同。
Copy after login

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) {
  }
 }
}
Copy after login

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();
}
Copy after login

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();
}
Copy after login

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;
}
Copy after login

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);
}
Copy after login

At this point, it meets the crash I encountered earlier:

java.lang.IllegalStateException: closed
Copy after login
Copy after login

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
Copy after login

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()
Copy after login

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

在Javascript中如何实现网页抢红包

详细解读ES6语法中可迭代协议

详细解读在React组件“外”如何使用父组件

微信小程序如何实现涂鸦

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!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

AI Hentai Generator

AI Hentai Generator

Generate AI Hentai for free.

Hot Article

R.E.P.O. Energy Crystals Explained and What They Do (Yellow Crystal)
3 weeks ago By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Best Graphic Settings
3 weeks ago By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. How to Fix Audio if You Can't Hear Anyone
3 weeks ago By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25: How To Unlock Everything In MyRise
3 weeks ago By 尊渡假赌尊渡假赌尊渡假赌

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

Convert basic data types to strings using Java's String.valueOf() function Convert basic data types to strings using Java's String.valueOf() function Jul 24, 2023 pm 07:55 PM

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

How to convert char array to string How to convert char array to string Jun 09, 2023 am 10:04 AM

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.

Use Java's String.replace() function to replace characters (strings) in a string Use Java's String.replace() function to replace characters (strings) in a string Jul 25, 2023 pm 05:16 PM

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:

2w words detailed explanation String, yyds 2w words detailed explanation String, yyds Aug 24, 2023 pm 03:56 PM

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 Use java's String.length() function to get the length of a string Jul 25, 2023 am 09:09 AM

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

Golang function byte, rune and string type conversion skills Golang function byte, rune and string type conversion skills May 17, 2023 am 08:21 AM

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.

How to use java's String class How to use java's String class Apr 19, 2023 pm 01:19 PM

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.

How to use the split method in Java String How to use the split method in Java String May 02, 2023 am 09:37 AM

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

See all articles