想必大家都用過或接觸過 OkHttp,我最近在使用 Okhttp 時(shí),就踩到一個(gè)坑,在這兒分享出來,以后大家遇到類似問題時(shí)就可以繞過去。
濟(jì)水街道網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)公司!從網(wǎng)頁設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、APP開發(fā)、響應(yīng)式網(wǎng)站開發(fā)等網(wǎng)站項(xiàng)目制作,到程序開發(fā),運(yùn)營維護(hù)。創(chuàng)新互聯(lián)公司從2013年開始到現(xiàn)在10年的時(shí)間,我們擁有了豐富的建站經(jīng)驗(yàn)和運(yùn)維經(jīng)驗(yàn),來保證我們的工作的順利進(jìn)行。專注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)公司。
只是解決問題是不夠的,本文將 側(cè)重從源碼角度分析下問題的根本,干貨滿滿。
1.發(fā)現(xiàn)問題
在開發(fā)時(shí),我通過構(gòu)造 OkHttpClient 對(duì)象發(fā)起一次請(qǐng)求并加入隊(duì)列,待服務(wù)端響應(yīng)后,回調(diào) Callback 接口觸發(fā) onResponse() 方法,然后在該方法中通過 Response 對(duì)象處理返回結(jié)果、實(shí)現(xiàn)業(yè)務(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)試,我打印了返回體,然后通過 parseResponseStr() 方法解析返回體(注意:這兒兩次調(diào)用了 response.body().string() )。
這段看起來沒有任何問題的代碼,實(shí)際運(yùn)行后卻出了問題:通過控制臺(tái)看到成功打印了返回體數(shù)據(jù)(json),但緊接著拋出了異常:
java.lang.IllegalStateException: closed
2.解決問題
檢查代碼后,發(fā)現(xiàn)問題出在調(diào)用 parseResponseStr() 時(shí),再次使用了 response.body().string() 作為參數(shù)。由于當(dāng)時(shí)趕時(shí)間,上網(wǎng)查閱后發(fā)現(xiàn) response.body().string() 只能調(diào)用一次,于是修改 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 { //此處,先將響應(yīng)體保存到內(nèi)存中 String responseStr = response.body().string(); if (BuildConfig.DEBUG) { Log.d(TAG, "onResponse: " + responseStr); } //解析請(qǐng)求體 parseReponseStr(responseStr); } });
3.結(jié)合源碼分析問題
問題解決了,事后還是要分析的。由于之前對(duì) OkHttp 的了解僅限于使用,沒有仔細(xì)分析過其內(nèi)部實(shí)現(xiàn)的細(xì)節(jié),周末抽時(shí)間往下看了看,算是弄明白了問題發(fā)生的原因。
先分析最直觀的問題:為何 response.body().string() 只能調(diào)用一次?
拆解來看,先通過 response.body() 得到 ResponseBody 對(duì)象(其是一個(gè)抽象類,在此我們不需要關(guān)心具體的實(shí)現(xiàn)類),然后調(diào)用 ResponseBody 的 string() 方法得到響應(yīng)體的內(nèi)容。
分析后 body() 方法沒有問題,我們往下看 string() 方法:
public final String string() throws IOException { return new String(bytes(), charset().name()); }
很簡(jiǎn)單,通過指定字符集(charset)將 byte() 方法返回的 byte[] 數(shù)組轉(zhuǎn)為 String 對(duì)象,構(gòu)造沒有問題,繼續(xù)往下看 byte() 方法:
public final byte[] bytes() throws IOException { //... BufferedSource source = source(); byte[] bytes; try { bytes = source.readByteArray(); } finally { Util.closeQuietly(source); } //... return bytes; } //... 表示刪減了無關(guān)代碼,下同。
在 byte() 方法中,通過 BufferedSource 接口對(duì)象讀取 byte[] 數(shù)組并返回。結(jié)合上面提到的異常,我注意到 finally 代碼塊中的 Util.closeQuietly() 方法。excuse me?默默地關(guān)閉???
這個(gè)方法看起來很詭異有木有,跟進(jìn)去看看:
public static void closeQuietly(Closeable closeable) { if (closeable != null) { try { closeable.close(); } catch (RuntimeException rethrown) { throw rethrown; } catch (Exception ignored) { } } }
原來,上面提到的 BufferedSource 接口,根據(jù)代碼文檔注釋,可以理解為 資源緩沖區(qū),其實(shí)現(xiàn)了 Closeable 接口,通過復(fù)寫 close() 方法來 關(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(); }
很明顯,通過 source.close() 關(guān)閉并釋放資源。說到這兒, closeQuietly() 方法的作用就不言而喻了,就是關(guān)閉 ResponseBody 子類所持有的 BufferedSource 接口對(duì)象。
分析至此,我們恍然大悟:當(dāng)我們第一次調(diào)用 response.body().string() 時(shí),OkHttp 將響應(yīng)體的緩沖資源返回的同時(shí),調(diào)用 closeQuietly() 方法默默釋放了資源。
如此一來,當(dāng)我們?cè)俅握{(diào)用 string() 方法時(shí),依然回到上面的 byte() 方法,這一次問題就出在了 bytes = source.readByteArray() 這行代碼。一起來看看 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; }
問題出在 for 循環(huán)的 source.read() 這兒。還記得在上面分析 close() 方法時(shí),其調(diào)用了 source.close() 來關(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ì)?
通過 fuc*ing the source code ,我們找到了問題的根本,但我還有一個(gè)疑問:OkHttp 為什么要這么設(shè)計(jì)?
其實(shí),理解這個(gè)問題最好的方式就是查看 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ù)必通過 Response.close() 來手動(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ì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)創(chuàng)新互聯(lián)網(wǎng)站的支持!
網(wǎng)頁標(biāo)題:OkHttp踩坑隨筆為何response.body().string()只能調(diào)用一次
標(biāo)題URL:http://muchs.cn/article36/igsppg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站策劃、靜態(tài)網(wǎng)站、自適應(yīng)網(wǎng)站、網(wǎng)站設(shè)計(jì)、服務(wù)器托管、全網(wǎng)營銷推廣
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)