OkHttp踩坑记:为何 response.body().string() 只能调用一次?
想必大家都用過(guò)或接觸過(guò) OkHttp,我最近在使用 Okhttp 時(shí),就踩到一個(gè)坑,在這兒分享出來(lái),以后大家遇到類似問(wèn)題時(shí)就可以繞過(guò)去。
只是解決問(wèn)題是不夠的,本文將?側(cè)重從源碼角度分析下問(wèn)題的根本,干貨滿滿。
1.發(fā)現(xiàn)問(wèn)題
在開(kāi)發(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() {在?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: closed2.解決問(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() {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ù)寫(xiě)?close()?方法來(lái)?關(guān)閉并釋放資源。接著往下看?close()?方法做了什么(在當(dāng)前場(chǎng)景下,BufferedSource?實(shí)現(xiàn)類為?RealBufferedSource):
//持有的 Source 對(duì)象 public final Source source;很明顯,通過(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()?方法:
繼續(xù)往下看?writeAll()?方法:
問(wèn)題出在?for?循環(huán)的?source.read()?這兒。還記得在上面分析?close()?方法時(shí),其調(diào)用了?source.close()?來(lái)關(guān)閉并釋放資源。那么,再次調(diào)用?read()?方法會(huì)發(fā)生什么呢:
至此,與我在前面遇到的崩潰對(duì)上了:
java.lang.IllegalStateException: closed4.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.
`** 于是我跑去看類注釋文檔,最后梳理如下:
5.總結(jié)
最后,總結(jié)以下幾點(diǎn)注意事項(xiàng),劃重點(diǎn)了:
轉(zhuǎn)載于:https://www.cnblogs.com/dongweiq/p/10373100.html
總結(jié)
以上是生活随笔為你收集整理的OkHttp踩坑记:为何 response.body().string() 只能调用一次?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 区块链中的基本概念整理
- 下一篇: 『计算机视觉』YOLO系列总结