首頁 web前端 js教程 為何response.body().string()不能實作多次呼叫?

為何response.body().string()不能實作多次呼叫?

Jun 13, 2018 am 10:31 AM
string

想必大家都用過或接觸過OkHttp,我最近在使用Okhttp 時,就踩到一個坑,在這兒分享出來,以後大家遇到類似問題時就可以繞過去

只是解決問題是不夠的,本文將著重從源碼角度分析下問題的根本,乾貨滿滿。

1.發現問題

在開發時,我透過建構OkHttpClient 物件發起一次請求並加入佇列,待服務端回應後,回呼  Callback 介面觸發  onResponse() 方法,然後在該方法中透過  Response 物件處理傳回結果、實作業務邏輯。程式碼大致如下:

//注:为聚焦问题,删除了无关代码
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());
  }
});
登入後複製

在onResponse() 中,為便於調試,我打印了返回體,然後通過  parseResponseStr() 方法解析返回體(注意:這兒兩次調用了  response.body(). string() )。

這段看起來沒有任何問題的程式碼,實際運行後卻出了問題:透過控制台看到成功列印了返回體資料(json),但緊接著拋出了異常:

java.lang.IllegalStateException: closed
登入後複製
登入後複製

2.解決問題

檢查程式碼後,發現問題出在呼叫parseResponseStr() 時,再次使用了  response.body().string () 作為參數。由於當時趕時間,上網查閱後發現  response.body().string() 只能呼叫一次,於是修改  onResponse() 方法中的邏輯後解決了問題:

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.結合源碼分析問題

問題解決了,事後還是要分析的。由於之前對 OkHttp 的了解僅限於使用,沒有仔細分析過其內部實現的細節,週末抽時間往下看了看,算是弄明白了問題發生的原因。

先分析最直覺的問題:為何 response.body().string() 只能呼叫一次?

拆解來看,先透過response.body() 得到  ResponseBody 物件(其是一個抽象類,在此我們不需要關心特定的實作類別),然後呼叫  ResponseBody 的  string() 方法得到回應體的內容。

分析後body() 方法沒有問題,我們往下看  string() 方法:

public final String string() throws IOException {
 return new String(bytes(), charset().name());
}
登入後複製

很簡單,透過指定字元集(charset)將byte() 方法傳回的  byte[ ] 陣列轉為  String 對象,建構沒有問題,繼續往下看  byte() 方法:

public final byte[] bytes() throws IOException {
 //...
 BufferedSource source = source();
 byte[] bytes;
 try {
  bytes = source.readByteArray();
 } finally {
  Util.closeQuietly(source);
 }
 //...
 return bytes;
}
//... 表示删减了无关代码,下同。
登入後複製

在byte() 方法中,透過  BufferedSource 介面物件讀取  byte[] 陣列並回傳。結合上面提到的異常,我注意到  finally 程式碼區塊中的  Util.closeQuietly() 方法。 excuse me?默默地關閉? ? ?

這個方法看起來很詭異有木有,跟進去看看:

public static void closeQuietly(Closeable closeable) {
 if (closeable != null) {
  try {
   closeable.close();
  } catch (RuntimeException rethrown) {
   throw rethrown;
  } catch (Exception ignored) {
  }
 }
}
登入後複製

原來,上面提到的BufferedSource 接口,根據代碼文檔註釋,可以理解為資源緩衝區,其實作了  Closeable 接口,透過複寫  close() 方法來關閉並釋放資源。接著往下看  close() 方法做了什麼(在目前場景下, BufferedSource 實作類別為  RealBufferedSource ):

//持有的 Source 对象
public final Source source;
@Override
public void close() throws IOException {
 if (closed) return;
 closed = true;
 source.close();
 buffer.clear();
}
登入後複製

很明顯,透過 source.close() 關閉並釋放資源。說到這兒,  closeQuietly() 方法的功能就不言而喻了,就是關閉  ResponseBody 子類別所持有的  BufferedSource 介面物件。

分析至此,我們恍然大悟:當我們第一次呼叫 response.body().string() 時,OkHttp 將回應體的緩衝資源回傳的同時,呼叫  closeQuietly() 方法默默釋放了資源。

如此一來,當我們再次呼叫 string() 方法時,依然回到上面的  byte() 方法,這次問題就出在了  bytes = source.readByteArray() 這一行程式碼。一起來看看  RealBufferedSource 的  readByteArray() 方法:

@Override
public byte[] readByteArray() throws IOException {
 buffer.writeAll(source);
 return buffer.readByteArray();
}
登入後複製

繼續往下看 writeAll() 方法:

@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;
}
登入後複製

問題出在 for 迴圈的  source.read() 這兒。還記得在上面分析  close() 方法時,其呼叫了  source.close() 來關閉並釋放資源。那麼,再呼叫  read() 方法會發生什麼事:

@Override
public long read(Buffer sink, long byteCount) throws IOException {
  //...
  if (closed) throw new IllegalStateException("closed");
  //...
  return buffer.read(sink, toRead);
}
登入後複製

至此,與我在前面遇到的崩潰對上了:

java.lang.IllegalStateException: closed
登入後複製
登入後複製

4.OkHttp為什麼要這麼設計?

透過 fuc*ing the source code ,我們找到了問題的根本,但我還有一個疑問:OkHttp 為什麼要這麼設計?

其實,理解這個問題最好的方式就是查看ResponseBody 的註釋文檔,正如  JakeWharton 在  issues 中給出的回复:

reply of JakeWharton in okhttp issues
登入後複製

就簡單的一句話: It's documented on ResponseBody.於是我跑去看類註解文檔,最後梳理如下:

在实际开发中,响应主体 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()
登入後複製

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

相关文章:

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

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

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

微信小程序如何实现涂鸦

以上是為何response.body().string()不能實作多次呼叫?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
2 週前 By 尊渡假赌尊渡假赌尊渡假赌
倉庫:如何復興隊友
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒險:如何獲得巨型種子
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

使用java的String.valueOf()函數將基本資料型別轉換為字串 使用java的String.valueOf()函數將基本資料型別轉換為字串 Jul 24, 2023 pm 07:55 PM

使用java的String.valueOf()函數將基本資料型別轉換為字串

怎麼把char數組轉string 怎麼把char數組轉string Jun 09, 2023 am 10:04 AM

怎麼把char數組轉string

使用java的String.replace()函數替換字串中的字元(字串) 使用java的String.replace()函數替換字串中的字元(字串) Jul 25, 2023 pm 05:16 PM

使用java的String.replace()函數替換字串中的字元(字串)

2w字 詳解 String,yyds 2w字 詳解 String,yyds Aug 24, 2023 pm 03:56 PM

2w字 詳解 String,yyds

Java String中的split方法如何使用 Java String中的split方法如何使用 May 02, 2023 am 09:37 AM

Java String中的split方法如何使用

使用java的String.length()函數取得字串的長度 使用java的String.length()函數取得字串的長度 Jul 25, 2023 am 09:09 AM

使用java的String.length()函數取得字串的長度

java的String類別如何使用 java的String類別如何使用 Apr 19, 2023 pm 01:19 PM

java的String類別如何使用

使用java的String.toLowerCase()函數將字串轉換為小寫 使用java的String.toLowerCase()函數將字串轉換為小寫 Jul 24, 2023 pm 11:52 PM

使用java的String.toLowerCase()函數將字串轉換為小寫

See all articles