作者:DylanCai
來源:https://juejin.im/post/5dadac2ae51d4524c3745219
前言
Retrofit
是目前主流的網絡請求架構,不少用過的小夥伴會遇到這樣的問題,絕大部分接口測試都正常,就個别接口尤其是傳回失敗資訊時報了個奇怪的錯誤資訊,而看了自己的代碼邏輯也沒什麼問題。别的接口都是一樣的寫,卻沒出現這樣的情況,可是背景人員看了也說不關他們的事。剛遇到時會比較懵,有些人不知道什麼原因也就無從下手。
問題原因
排查問題也很簡單,把資訊百度一下,會發現是解析異常。那就先看下背景傳回了什麼,用 PostMan 請求一下檢視傳回結果,發現是類似下面這樣的:
1{
也可能是這樣的:
1{
或者是這樣的:
1{
仔細觀察後突然恍然大悟,這不是坑爹嗎?背景這樣傳回解析肯定有問題呀,我要将 data
解析成一個對象,而背景傳回的是一個空字元串、整形或空數組,肯定解析報錯。
嗯,這就是背景的問題,是背景寫得不“規範”,是以就跑過去和背景理論讓他們改。如果背景是比較好說話,肯配合改還好說。但有些可能是比較“倔強”的性格,可能會說,“這很簡單呀,知道是失敗狀态不解析 data 不就好了?” ,或者說, “為什麼 iOS 可以,你這邊卻不行?你們 Android 有問題就不能自己處理掉嗎?” 。如果遇到這樣的同僚就會比較尴尬。
其實就算背景能根據我們要求改,但也不是長遠之計。背景人員變動或自己換個環境可能還是會遇到同樣的情況,每次都和背景溝通配合改也麻煩,而且沒準就剛好遇到“倔強”不肯改的。
是背景人員寫得不規範嗎?我個人認為并不是,因為并沒有約定俗成的規範要這麼寫,其實隻是背景人員不知道這麼傳回資料會對 Retrofit
的解析有影響,不知道這麼寫對 Android
不太友好。背景人員也沒有錯,我們所覺得的“規範”沒人告訴過他呀。可以通過溝通解決問題,不過建議還是自己把問題處理了,一勞永逸。
解決方案
既然是解析報錯了,那麼在 Gson 解析成對象之前,先驗證狀态碼,判斷是錯誤的情況就抛出異常,這樣就不進行後續的 Gson 解析操作去解析
data,也就沒問題了。
最先想到的當然是從解析的地方入手,而 Retrofit 能進行 Gson 解析是配置了一個 Gson 轉換器。
1retrofit = Retrofit.Builder()
是以我們修改 GsonConverterFactory 不就好了。
自定義 GsonConverterFactory 處理傳回結果
試一下會發現并不能直接繼承 GsonConverterFactory 重載修改相關方法,因為該類用了 final 修飾。是以隻好把
GsonConverterFactory 源碼複制出來改,其中關聯的兩個類 GsonRequestBodyConverter 和
GsonResponseBodyConverter 也要複制修改。下面給出的是 Kotlin 版本的示例。
1
上面三個類中隻需要修改 GsonResponseBodyConverter
的代碼,因為是在這個類解析資料。可以在上面有注釋的地方加入自己的處理。到底加什麼代碼,看完後面的内容就知道了。
雖然得到了我們想要的效果,但總感覺并不是很優雅,因為這隻是在 gson 解析之前增加一些判斷,而為此多寫了很多和源碼重複的代碼。并且存在個問題,這是針對
Retrofit 進行處理的,如果公司用的是自己封裝的 OkHttp 請求工具,就沒法用這個方案了。
觀察一下發現其實隻是對一個 ResponseBody 對象進行解析判斷狀态碼,就是說隻需要得到個 ResponseBody 對象而已。那麼還有什麼辦法能在
gson 解析之前拿到 ResponseBody 呢?
自定義攔截器處理傳回結果
很容易會想到用攔截器,按道理來說是應該是可行的,通過攔截器處理也不局限于使用 Retrofit,用 OkHttp 的也能處理。
想法很美好,但是實際操作起來并沒有想象中的簡單。剛開始可能會想到用
response.body().string()
讀出 json 字元串。
1
看着好像沒問題,但是嘗試後發現,狀态碼是失敗的情況确實沒毛病,然而狀态碼是正确的情況卻有問題了。
為什麼會這樣子?有興趣的可以看下這篇文章《為何 response.body().string() 隻能調用一次?》。簡單總結一下就是考慮到應用重複讀取資料的可能性很小,是以将其設計為一次性流,讀取後即關閉并釋放資源。我們在攔截器裡用通常的
Response 使用方法會把資源釋放了,後續解析沒有資源了就會有問題。
那該怎麼辦呢?自己對 Response 的使用又不熟悉,怎麼知道該怎麼讀資料不影響後續的操作。可以參考源碼呀,OkHttp
也是用了一些攔截器處理響應資料,它卻沒有釋放掉資源。
這裡就不用大家去看源碼研究怎麼寫的了,我直接封裝好一個工具類提供大家使用,已經把響應資料的字元串得到了,大家可以直接編寫自己的業務代碼,拷貝下面的類使用即可。
1
由于 OkHttp 源碼已經用 Kotlin 語言重寫了,是以隻有個 Kotlin 版本的。但是可能還有很多人還沒有用 Kotlin
寫項目,是以個人又手動翻譯了一個 Java 版本的,友善大家使用,同樣拷貝使用即可。
1
主要是拿到 source 再獲得 buffer,然後通過 buffer 去讀出字元串。說下其中的一段
gzip
相關的代碼,為什麼需要有這段代碼的處理,自己看源碼的話可能會漏掉。這是因為 OkHttp 請求時會添加支援
gzip
壓縮的預處理,是以如果響應的資料是
gzip
編碼的,需要對
gzip
壓縮資料解包再去讀資料。
好了廢話不多說,到底這個工具類怎麼用,其實和攔截器一樣使用,繼承我封裝好的
ResponseBodyInterceptor
類,在重寫方法裡加上自己需要的業務處理代碼,body 參數就是我們想要的 json
字元串資料,可以進行解析判斷狀态碼是失敗情況并抛出異常。下面給一個簡單的解析例子參考,json 結構是文章開頭給出的例子,這裡假設狀态碼不是 200
都抛出異常。
1
然後在 OkHttpClient 中添加該攔截器就可以了。
1
萬一背景傳回的是更騷的資料呢?
怎麼個騷法?有位小夥伴提到的,騷的時候資料還會去 msg 取。好,我們也來幫他處理了。假設傳回的資料是下面這樣的:
1{
通常 msg 傳回的是個字元串,但這次居然是個對象,而且是我們需要得到的資料。我們解析的實體類已經定義了 msg 是字元串,當然不可能因為一個接口把 msg
改成泛型,是以我們需要偷偷地把資料改成我們想要得到的形式。
1{
那麼該怎麼操作呢?代碼比較簡單,就不啰嗦了。記得要把該攔截器添加上。
1
如果用 Java 寫的話,可以這樣重新生成響應對象。
1
這樣就不管背景傳回怎樣的騷資料,我們根據情況先轉成自己想要的資料,再進行解析就沒有問題了。
總結
如果隻是遇到開頭說的 data 類型不定的情況,可以和背景溝通處理,不過還是建議我們也進行處理,加多一層保障,提高 App
的健壯性。如果是後面說的特别騷的傳回資料,就建議直接讓背景改,實在太騷了不應該縱容。
自定義 GsonConverter 會寫不少與源碼重複的代碼,并不推薦,而且受限于
Retrofit,使用攔截器處理更加的通用。難度在于攔截器該怎麼寫,是以封裝好了工具類供大家使用。
剛開始隻是打算分享自己封裝好的類,說一下怎麼使用來解決問題。不過後來還是花了很多篇幅較長的描述了我解決問題的整個心路曆程,主要是見過太多人求助這類問題,是以就寫詳細一點,後續如果還有人問就直接發文章過去,應該能有效解決他的疑惑。另外如果公司用的請求架構即不是
Retrofit 也不是基于 OkHttp 封裝的架構的話,通過本文章的解決問題思路應該也能尋找到相應的解決方案。
如果大家有見到問這類問題的小夥伴,也可以直接将本文章分享給他們喲~
推薦閱讀
一步步帶你讀懂 Okhttp 源碼Android 點九圖機制講解及在聊天氣泡中的應用職場上這四件事,越早知道越好自定義View之雙層波紋氣泡(xFermode)Android-自定義氣泡View,讓我們告别.9圖面試官:今日頭條啟動很快,你覺得可能是做了哪些優化?花式實作時間軸,樣式由你來定!
ConstraintLayout使用指南
面試官:你有m個雞蛋,如何用最少的次數測出雞蛋會在哪一層碎?
Android自定義View-簡約風歌詞控件
花式實作時間軸,樣式由你來定!
面試官:Android 子線程更新UI了解嗎?
掃一掃,歡迎關注我的微信公衆号 stormjun94(徐公碼字)