搜尋
首頁web前端js教程為何response.body().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
JavaScript的角色:使網絡交互和動態JavaScript的角色:使網絡交互和動態Apr 24, 2025 am 12:12 AM

JavaScript是現代網站的核心,因為它增強了網頁的交互性和動態性。 1)它允許在不刷新頁面的情況下改變內容,2)通過DOMAPI操作網頁,3)支持複雜的交互效果如動畫和拖放,4)優化性能和最佳實踐提高用戶體驗。

C和JavaScript:連接解釋C和JavaScript:連接解釋Apr 23, 2025 am 12:07 AM

C 和JavaScript通過WebAssembly實現互操作性。 1)C 代碼編譯成WebAssembly模塊,引入到JavaScript環境中,增強計算能力。 2)在遊戲開發中,C 處理物理引擎和圖形渲染,JavaScript負責遊戲邏輯和用戶界面。

從網站到應用程序:JavaScript的不同應用從網站到應用程序:JavaScript的不同應用Apr 22, 2025 am 12:02 AM

JavaScript在網站、移動應用、桌面應用和服務器端編程中均有廣泛應用。 1)在網站開發中,JavaScript與HTML、CSS一起操作DOM,實現動態效果,並支持如jQuery、React等框架。 2)通過ReactNative和Ionic,JavaScript用於開發跨平台移動應用。 3)Electron框架使JavaScript能構建桌面應用。 4)Node.js讓JavaScript在服務器端運行,支持高並發請求。

Python vs. JavaScript:比較用例和應用程序Python vs. JavaScript:比較用例和應用程序Apr 21, 2025 am 12:01 AM

Python更適合數據科學和自動化,JavaScript更適合前端和全棧開發。 1.Python在數據科學和機器學習中表現出色,使用NumPy、Pandas等庫進行數據處理和建模。 2.Python在自動化和腳本編寫方面簡潔高效。 3.JavaScript在前端開發中不可或缺,用於構建動態網頁和單頁面應用。 4.JavaScript通過Node.js在後端開發中發揮作用,支持全棧開發。

C/C在JavaScript口譯員和編譯器中的作用C/C在JavaScript口譯員和編譯器中的作用Apr 20, 2025 am 12:01 AM

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。1)C 用于解析JavaScript源码并生成抽象语法树。2)C 负责生成和执行字节码。3)C 实现JIT编译器,在运行时优化和编译热点代码,显著提高JavaScript的执行效率。

JavaScript在行動中:現實世界中的示例和項目JavaScript在行動中:現實世界中的示例和項目Apr 19, 2025 am 12:13 AM

JavaScript在現實世界中的應用包括前端和後端開發。 1)通過構建TODO列表應用展示前端應用,涉及DOM操作和事件處理。 2)通過Node.js和Express構建RESTfulAPI展示後端應用。

JavaScript和Web:核心功能和用例JavaScript和Web:核心功能和用例Apr 18, 2025 am 12:19 AM

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。

了解JavaScript引擎:實施詳細信息了解JavaScript引擎:實施詳細信息Apr 17, 2025 am 12:05 AM

理解JavaScript引擎內部工作原理對開發者重要,因為它能幫助編寫更高效的代碼並理解性能瓶頸和優化策略。 1)引擎的工作流程包括解析、編譯和執行三個階段;2)執行過程中,引擎會進行動態優化,如內聯緩存和隱藏類;3)最佳實踐包括避免全局變量、優化循環、使用const和let,以及避免過度使用閉包。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

SublimeText3 英文版

SublimeText3 英文版

推薦:為Win版本,支援程式碼提示!

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),