欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

 更新時(shí)間:2018年01月08日 13:41:51   作者:ruicbAndroid  
想必大家都用過(guò)或接觸過(guò) OkHttp,我最近在使用 Okhttp 時(shí),就踩到一個(gè)坑,在這兒分享出來(lái),以后大家遇到類似問(wèn)題時(shí)就可以繞過(guò)去

想必大家都用過(guò)或接觸過(guò) OkHttp,我最近在使用 Okhttp 時(shí),就踩到一個(gè)坑,在這兒分享出來(lái),以后大家遇到類似問(wèn)題時(shí)就可以繞過(guò)去。

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

1.發(fā)現(xiàn)問(wèn)題

在開發(fā)時(shí),我通過(guò)構(gòu)造 OkHttpClient 對(duì)象發(fā)起一次請(qǐng)求并加入隊(duì)列,待服務(wù)端響應(yīng)后,回調(diào)  Callback 接口觸發(fā)  onResponse() 方法,然后在該方法中通過(guò)  Response 對(duì)象處理返回結(jié)果、實(shí)現(xiàn)業(yè)務(wù)邏輯。代碼大致如下:

//注:為聚焦問(wèn)題,刪除了無(wú)關(guān)代碼
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());
    }
    //解析請(qǐng)求體
    parseResponseStr(response.body().string());
  }
});

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

這段看起來(lái)沒(méi)有任何問(wèn)題的代碼,實(shí)際運(yùn)行后卻出了問(wèn)題:通過(guò)控制臺(tái)看到成功打印了返回體數(shù)據(jù)(json),但緊接著拋出了異常:

java.lang.IllegalStateException: closed

2.解決問(wèn)題

檢查代碼后,發(fā)現(xiàn)問(wèn)題出在調(diào)用 parseResponseStr() 時(shí),再次使用了  response.body().string() 作為參數(shù)。由于當(dāng)時(shí)趕時(shí)間,上網(wǎng)查閱后發(fā)現(xiàn)  response.body().string() 只能調(diào)用一次,于是修改  onResponse() 方法中的邏輯后解決了問(wèn)題:

getHttpClient().newCall(request).enqueue(new Callback() {
  @Override
  public void onFailure(Call call, IOException e) {}
  @Override
  public void onResponse(Call call, Response response) throws IOException {
    //此處,先將響應(yīng)體保存到內(nèi)存中
    String responseStr = response.body().string();
    if (BuildConfig.DEBUG) {
      Log.d(TAG, "onResponse: " + responseStr);
    }
    //解析請(qǐng)求體
    parseReponseStr(responseStr);
  }
});

3.結(jié)合源碼分析問(wèn)題

問(wèn)題解決了,事后還是要分析的。由于之前對(duì) OkHttp 的了解僅限于使用,沒(méi)有仔細(xì)分析過(guò)其內(nèi)部實(shí)現(xiàn)的細(xì)節(jié),周末抽時(shí)間往下看了看,算是弄明白了問(wèn)題發(fā)生的原因。

先分析最直觀的問(wèn)題:為何 response.body().string() 只能調(diào)用一次?

拆解來(lái)看,先通過(guò) response.body() 得到  ResponseBody 對(duì)象(其是一個(gè)抽象類,在此我們不需要關(guān)心具體的實(shí)現(xiàn)類),然后調(diào)用  ResponseBody 的  string() 方法得到響應(yīng)體的內(nèi)容。

分析后 body() 方法沒(méi)有問(wèn)題,我們往下看  string() 方法:

public final String string() throws IOException {
 return new String(bytes(), charset().name());
}

很簡(jiǎn)單,通過(guò)指定字符集(charset)將 byte() 方法返回的  byte[] 數(shù)組轉(zhuǎn)為  String 對(duì)象,構(gòu)造沒(méi)有問(wèn)題,繼續(xù)往下看  byte() 方法:

public final byte[] bytes() throws IOException {
 //...
 BufferedSource source = source();
 byte[] bytes;
 try {
  bytes = source.readByteArray();
 } finally {
  Util.closeQuietly(source);
 }
 //...
 return bytes;
}
//... 表示刪減了無(wú)關(guān)代碼,下同。

在 byte() 方法中,通過(guò)  BufferedSource 接口對(duì)象讀取  byte[] 數(shù)組并返回。結(jié)合上面提到的異常,我注意到  finally 代碼塊中的  Util.closeQuietly() 方法。excuse me?默默地關(guān)閉???

這個(gè)方法看起來(lái)很詭異有木有,跟進(jìn)去看看:

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

原來(lái),上面提到的 BufferedSource 接口,根據(jù)代碼文檔注釋,可以理解為 資源緩沖區(qū),其實(shí)現(xiàn)了  Closeable 接口,通過(guò)復(fù)寫  close() 方法來(lái) 關(guān)閉并釋放資源。接著往下看  close() 方法做了什么(在當(dāng)前場(chǎng)景下, BufferedSource 實(shí)現(xiàn)類為  RealBufferedSource ):

//持有的 Source 對(duì)象
public final Source source;
@Override
public void close() throws IOException {
 if (closed) return;
 closed = true;
 source.close();
 buffer.clear();
}

很明顯,通過(guò) source.close() 關(guān)閉并釋放資源。說(shuō)到這兒,  closeQuietly() 方法的作用就不言而喻了,就是關(guān)閉  ResponseBody 子類所持有的  BufferedSource 接口對(duì)象。

分析至此,我們恍然大悟:當(dāng)我們第一次調(diào)用 response.body().string() 時(shí),OkHttp 將響應(yīng)體的緩沖資源返回的同時(shí),調(diào)用  closeQuietly() 方法默默釋放了資源。

如此一來(lái),當(dāng)我們?cè)俅握{(diào)用 string() 方法時(shí),依然回到上面的  byte() 方法,這一次問(wèn)題就出在了  bytes = source.readByteArray() 這行代碼。一起來(lái)看看  RealBufferedSource 的  readByteArray() 方法:

@Override
public byte[] readByteArray() throws IOException {
 buffer.writeAll(source);
 return buffer.readByteArray();
}

繼續(xù)往下看 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;
}

問(wèn)題出在 for 循環(huán)的  source.read() 這兒。還記得在上面分析  close() 方法時(shí),其調(diào)用了  source.close() 來(lái)關(guān)閉并釋放資源。那么,再次調(diào)用  read() 方法會(huì)發(fā)生什么呢:

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

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

java.lang.IllegalStateException: closed

4.OkHttp 為什么要這么設(shè)計(jì)?

通過(guò) fuc*ing the source code ,我們找到了問(wèn)題的根本,但我還有一個(gè)疑問(wèn):OkHttp 為什么要這么設(shè)計(jì)?

其實(shí),理解這個(gè)問(wèn)題最好的方式就是查看 ResponseBody 的注釋文檔,正如  JakeWharton 在  issues 中給出的回復(fù):

reply of JakeWharton in okhttp issues

就簡(jiǎn)單的一句話: It's documented on ResponseBody. 于是我跑去看類注釋文檔,最后梳理如下:

在實(shí)際開發(fā)中,響應(yīng)主體 RessponseBody 持有的資源可能會(huì)很大,所以 OkHttp 并不會(huì)將其直接保存到內(nèi)存中,只是持有數(shù)據(jù)流連接。只有當(dāng)我們需要時(shí),才會(huì)從服務(wù)器獲取數(shù)據(jù)并返回。同時(shí),考慮到應(yīng)用重復(fù)讀取數(shù)據(jù)的可能性很小,所以將其設(shè)計(jì)為 一次性流(one-shot) ,讀取后即 '關(guān)閉并釋放資源'。

5.總結(jié)

最后,總結(jié)以下幾點(diǎn)注意事項(xiàng),劃重點(diǎn)了:

1.響應(yīng)體只能被使用一次;

2.響應(yīng)體必須關(guān)閉:值得注意的是,在下載文件等場(chǎng)景下,當(dāng)你以  response.body().byteStream()  形式獲取輸入流時(shí),務(wù)必通過(guò)  Response.close()  來(lái)手動(dòng)關(guān)閉響應(yīng)體。

3.獲取響應(yīng)體數(shù)據(jù)的方法:使用  bytes()  或  string()  將整個(gè)響應(yīng)讀入內(nèi)存;或者使用  source() ,  byteStream() ,  charStream()  方法以流的形式傳輸數(shù)據(jù)。

4.以下方法會(huì)觸發(fā)關(guān)閉響應(yīng)體:

Response.close()
Response.body().close()
Response.body().source().close()
Response.body().charStream().close()
Response.body().byteString().close()
Response.body().bytes()
Response.body().string()

總結(jié)

以上所述是小編給大家介紹的OkHttp踩坑隨筆為何 response.body().string() 只能調(diào)用一次,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!

相關(guān)文章

最新評(píng)論