隨着Android的發展,各路大神的貢獻,我們可用的輪子越來越多。比如HTTP請求框架,有自家的Volley,Square的okhttp, async-http-lib, 還有聚合版的xUtils以及AFinal。我想你肯定用過其中一個。
當然Stay今天不是來科普的,而是來跟大家一起思考一個問題的。我們暫且不提他們在內部做了多少優化,我們就說lib的返回數據。
在常用的http請求的返回值中,文件,JSON占絕大多數(圖片有其他框架,這里不考慮)。文件下載都有專門的response,會幫你下載到制定路徑,這個肯定都支持。那JSON呢?貌似都返回一個JSONObject或者JSONArray。
我去,做好事得做全啊,返回JSONObject是個什么鬼,難道還得自己動手寫解析反序列化成自己要得對象?那是最低級的程序員干的事。好在我們都不傻,還有GSON,fastJson,Jackson幫我們來完成這步轉化。
比方說服務器返回的數據:(雙引號沒加,占位置,別噴)
{name:stay, age:17, job:soho}
對應的對象:
Class User{ public String name, public int age, public String job }
好,那我們只需要在response回調時拿到result,調用json-lib反序列化就可以了,比如這樣:
User user = gson.fromJson(result, User.class)
現在我們就可以使用user對象來更新UI了對吧。就多了一行代碼,沒強迫症的也就忍過去了。
接下來我們再看下面一種json數據:
{resCode:200, data:{name:Stay, age:17, job:soho}, msg:success} {resCode:401, data:{}, msg:token invalid}
我去,這是什么鬼,不好好遵守http協議,統一返回200是什么鬼,token不合法給我返回401 error code不好嗎。。別說,很多公司都這么定義返回數據的
這樣我們怎么辦。。多寫一步解析咯。
JSONObject json = new JSONObject(result) JSONObject data = json.optJSONObject("data") if(data != null){ User user = gson.fromJson(data.toString(), User.class) }
天啊,即使沒強迫症,大概也會受不了每個API請求都寫這么多代碼了吧。
BB了這么多,大家應該懂我想表達什么了吧?
為什么不直接將json轉換成我們要的對象User再回調呢?
而且在json數據大的情況下,反序列化還是耗時操作,有可能會卡UI的好嗎。
這可能么?當然可以,不然Stay鋪墊這么多干嘛。不過在Stay說解決方案之前,大家可以試着自己考慮下實現。
-
我們拿到的是String,格式是JSON
-
每次拿到JSON String,我們都來做了一步反序列化對象操作
-
gson.fromJson需要兩個參數(String JSON,Class dest)
-
回調參數得變成onResponse(User user)
-
框架層得知道Class dest
如果能把這些事情想清楚,你就可以很順利得擴展那些開源框架了,以后你也再不用手寫json解析了。
就說這么多,留點時間給大家自己思考下,下面再說解決方案😳
最后說下需要用到得知識點:泛型,反射
上文中,我們提到,能否讓我們的HTTP框架幫我們完成自動反序列化的操作。同時也給大家做了些提示:泛型和反射。
現在我們以Volley為例:
在Volley中有三種Request:FileRequest,StringRequest,ImageRequest。
JSON數據也是字符串,所以我們要重寫StringRequest中的部分方法就可以咯。
看下StringRequest源碼,你會看到解析服務器byte[]到String的是parseNetworkResponse(NetworkResponse response),解析完String直接就return給外層了。
這里我們也采用相同的方式,創建一個GsonRequest< T >繼承Request< T >, 至於實現,先把StringRequest的代碼copy過來。唯一不同的是,StringRequest因為指定返回String類型數據所以不需要泛型。
在parseNetworkResponse(NetworkResponse response)中,我們引入gson來反序列化json string,T的class怎么辦呢?你可以通過外層顯式的傳進來或者通過反射來拿類上的泛型T的type。兩種都可以。
具體到代碼:
擴展完畢,你只需要new GsonRequest,聲明好泛型T,等待接收t對象回調就好啦。
如果你想知道這種擴展是如何一步步推導出來的,可以看Stay錄的專題視頻。
傳送門:預處理服務器返回的數據(JSON轉對象)
像這樣的擴展還有很多,框架不是萬能的,要合理的根據自己的需求定制你想要的框架。
最后,留個問題給大家,如果是服務器返回了1M的JSON數據,還能用上述擴展么?如果不可以,那該怎么辦呢?
在JSON反序列化的最后,有提到,如果有1M的JSON文本應該如何來解析?
1M的JSON String,不管用GSON,fastjson,jackson,估計都要OOM了吧。本來我想說200M的JSON數據的,想想這太坑了,就改說1M了。
答案,用JsonReader讀流。比如說:
public User readUser(JsonReader reader) throws IOException { reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); if (name.equals("name")) { username = reader.nextString(); } else if (name.equals("followers_count")) { followersCount = reader.nextInt(); } else { reader.skipValue(); } } reader.endObject(); return new User(username, followersCount); }
我去,要手寫JSON解析了,這太麻煩了吧。。。
但是你想,跟性能比起來,這些體力也不算什么了吧。
上述沒太多特別的地方,你可以直接看JsonReader的源碼注釋,里面有詳細的用法示例。
在這里呢,我們先說說如何讓JsonReader來讀大JSON文本。
FileReader in = new FileReader(path); JsonReader reader = new JsonReader(in);
首先,你得先把JSON文本以文件的形式存到SD卡上。再通過FileReader拿到文件流,再通過JsonReader來讀流,讀流的方式也就意味着是順序讀的,所以即使它不是正確的json格式,也會一直讀到錯誤為止。
JsonReader對手寫的json解析語法非常嚴格,寫錯是非常頭疼的事,另外建議把nodeName變為常量去做判斷,不然以后改變量名得哭瞎。
當然,Stay肯定不會講這么簡單的東西,我們怎么跟HTTP框架結合在一起呢?這解析過程肯定也是耗時操作,我總不能先用框架把數據當文件下載下來,然后再開一個線程來解析吧。這才是最蛋疼的地方。
可惜原生Volley都不支持文件下載,這里我就拿自己的HTTP框架做演示了。
簡單說下實現過程:
- 首先寫個接口,比如JsonReaderable,里面定義一個方法readFromJson(JsonReader reader)
- 讓你想要被反序列化的對象pojo實現這個接口,比如這樣
- 讓框架先把數據當文件下載到SD卡
- 在callback之前再bindData,比如這樣
這樣就能將json數據自動反序列化成對象callback回去了。你只需要在每個對象pojo中實現readFromJson方法就好了。
- 如果是jsonarray怎么辦,我們要返回一個ArrayList啊。比如這樣
- 一個好的框架相當的重要啊,我們再來看外層的調用
應該不用解釋吧,都能看懂。
這種情況雖然比較少見,但在一些erp啊,sap項目中經常會遇到(別問Stay怎么知道)如果你也見過Android上500M的數據庫,那這些心得你都能自己領悟到了。
現在我們在App中基本采取的都是分頁,一般來說不需要用JsonReader,但如果Json數據超過10K以上,pojo的復雜度特別高,並且還有嵌套時,也應該考慮使用。
你也許會問,500M,即使用JsonReader讀流生成對象了,內存也裝不下呀。沒事,你可以通過ormapping型數據庫框架來存數據,比如說讀200個對象存一次,清一次。或者你可以用接口回調的方式扔給外層處理,onPartialDataBinding(ArrayList list)
其實這個擴展其他第三方框架也沒什么問題,只要思路有了,實現起來也就很容易了。
框架最好是根據App具體的需求以及使用場景來定制,僅會調用哪些開源lib,看不懂,改不了,這樣只能讓自己在技術路上越走越窄。
就寫到這里,別問Stay要代碼哈,只講思維與解決方案。