中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

OkHttp踩坑隨筆為何 response.body().string() 只能調用一次

發布時間:2020-09-15 09:31:18 來源:腳本之家 閱讀:197 作者:ruicbAndroid 欄目:web開發

想必大家都用過或接觸過 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()

總結

以上所述是小編給大家介紹的OkHttp踩坑隨筆為何 response.body().string() 只能調用一次,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對億速云網站的支持!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

丹棱县| 新化县| 武汉市| 宜川县| 山东省| 安图县| 临猗县| 北安市| 静宁县| 英德市| 克拉玛依市| 海安县| 从江县| 延寿县| 潮州市| 策勒县| 东港市| 清镇市| 沾益县| 赤城县| 鄂尔多斯市| 乐至县| 东兴市| 沅江市| 隆昌县| 弥勒县| 瓮安县| 大足县| 南华县| 东城区| 佛教| 府谷县| 丹棱县| 峨眉山市| 昭平县| 丰城市| 化隆| 徐水县| 江都市| 镇沅| 资讯|