日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

OkHttp踩坑记:为何 response.body().string() 只能调用一次?

發布時間:2025/4/14 编程问答 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 OkHttp踩坑记:为何 response.body().string() 只能调用一次? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

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

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

1.發現問題

在開發時,我通過構造?OkHttpClient?對象發起一次請求并加入隊列,待服務端響應后,回調?Callback?接口觸發?onResponse()?方法,然后在該方法中通過?Response?對象處理返回結果、實現業務邏輯。代碼大致如下:

//注:為聚焦問題,刪除了無關代碼 getHttpClient().newCall(request).enqueue(new Callback() {@Overridepublic 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() {@Overridepublic 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.總結

最后,總結以下幾點注意事項,劃重點了:

  • 響應體只能被使用一次;
  • 響應體必須關閉:值得注意的是,在下載文件等場景下,當你以?response.body().byteStream()?形式獲取輸入流時,務必通過?Response.close()?來手動關閉響應體。
  • 獲取響應體數據的方法:使用?bytes()?或?string()?將整個響應讀入內存;或者使用?source(),?byteStream(),?charStream()?方法以流的形式傳輸數據。
  • 以下方法會觸發關閉響應體:
  • Response.close() Response.body().close() Response.body().source().close() Response.body().charStream().close() Response.body().byteString().close() Response.body().bytes() Response.body().string()

    轉載于:https://www.cnblogs.com/dongweiq/p/10373100.html

    總結

    以上是生活随笔為你收集整理的OkHttp踩坑记:为何 response.body().string() 只能调用一次?的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。