網(wǎng)絡(luò)請(qǐng)求API
在Android上昆咽,原生API有兩個(gè)实辑,HttpUrlConnection和HttpClient,它們對(duì)封裝Socket進(jìn)行封裝季春,讓HTTP請(qǐng)求變得簡(jiǎn)單。這應(yīng)該也算框架吧消返?
想象下载弄,如果沒(méi)有HttpUrlConnection和HttpClient,一次性的API請(qǐng)求得有多麻煩撵颊。
現(xiàn)在宇攻,我們又多了一種OkHttp,Square出品倡勇。當(dāng)然底層還是封裝socket逞刷。為什么,為什么還要再出一個(gè)OkHttp妻熊,吃飽了撐的夸浅?肯定不是,那究竟有什么好的扔役?自己動(dòng)手查一下吧帆喇。
我們假設(shè)一下,應(yīng)該是HttpUrlConnection和HttpClient自身有bug和缺陷亿胸,所以才會(huì)再根據(jù)如今的網(wǎng)絡(luò)情況設(shè)計(jì)OkHttp吧坯钦。
如果你看過(guò)Volley的源代碼预皇,就知道當(dāng)SDK>9時(shí),默認(rèn)使用HttpUrlConnection婉刀,<9的就用HttpClient吟温。
既然>9采用HttpUrlConnection了,那說(shuō)明突颊,再以后的版本中由Android修復(fù)了鲁豪,那HttpClient呢,Apache更新維護(hù)太慢律秃,基本要被淘汰爬橡。
如果說(shuō)你的項(xiàng)目還在用HttpClient,甚至還在為HttpClient的某些bug而苦惱友绝,那么你該考慮是否該換了。畢竟現(xiàn)實(shí)不可能給你那么多時(shí)間去調(diào)研debug肝劲。
當(dāng)然OkHttp也是有bug的迁客,從github上的issues就能知道,如果你用OkHttp發(fā)現(xiàn)了bug辞槐,又不知道如何解決掷漱,不妨去那看看。
說(shuō)了這么多榄檬,Stay想表達(dá)的有兩層意思:
- 不妨使用新技術(shù)來(lái)解決老技術(shù)的缺陷卜范,就好像如果現(xiàn)在還有人用TabActivity,TabHost鹿榜,那給人感覺(jué)一定是做外包出身的海雪。
- 嘗試新技術(shù)的成本不高的,如果它開源舱殿,并且有release版本(1.0+)奥裸,你都可以集成試試。新技術(shù)都是為了更好的開發(fā)而被設(shè)計(jì)出來(lái)的沪袭,就算它不是最優(yōu)的解決方案湾宙,至少設(shè)計(jì)理念,解決思路是值得參考的冈绊。
今天下午花了點(diǎn)時(shí)間侠鳄,粗略的過(guò)了一遍OkHttp,有意思的是死宣,為了讓大家無(wú)縫集成伟恶,也是蠻拼的,額外提供HttpUrlConnection和HttpClient的寫法毅该。你只需要再依賴okhttp-urlconnection.jar或者okhttp-apache.jar就可以了知押。
本來(lái)Stay是打算用OkHttp自己的請(qǐng)求API集成的自己的網(wǎng)絡(luò)框架里叹螟,搗鼓了半天,怪麻煩的台盯,API來(lái)來(lái)回回要找半天罢绽,索性就直接換成HttpUrlConnection的形式寫了。
see, 集成起來(lái)太方便了静盅。簡(jiǎn)單的測(cè)試了下良价,get,post蒿叠,上傳明垢,下載都沒(méi)問(wèn)題。其他就沒(méi)再深入了市咽。
OkHttp的示例都很簡(jiǎn)單痊银,有很多配置(ssl, cookies, headers, timeout)沒(méi)詳細(xì)說(shuō)明,那如果你想配置施绎,該怎么做捏溯革。可以看源碼谷醉,也可以看一些網(wǎng)絡(luò)請(qǐng)求框架如:Retrofit(Square的網(wǎng)絡(luò)請(qǐng)求框架致稀,默認(rèn)集成OkHttp)源碼中的API配置。
對(duì)于這類底層API創(chuàng)新俱尼,還是比較少的抖单,幾年內(nèi)能出一個(gè)就不錯(cuò)了,畢竟對(duì)HTTP協(xié)議融會(huì)貫通而且能優(yōu)化的大牛少之又少遇八。對(duì)于一般的苦逼開發(fā)者來(lái)說(shuō)矛绘,能做到及時(shí)跟進(jìn)已屬不易。
多嘗試新技術(shù)刃永,至少可以晚些結(jié)束自己的程序員生涯 :)
JSON數(shù)據(jù)
隨著Android的發(fā)展蔑歌,各路大神的貢獻(xiàn),我們可用的輪子越來(lái)越多揽碘。比如HTTP請(qǐng)求框架次屠,有自家的Volley,Square的okhttp, async-http-lib, 還有聚合版的xUtils以及AFinal雳刺。我想你肯定用過(guò)其中一個(gè)劫灶。
當(dāng)然Stay今天不是來(lái)科普的,而是來(lái)跟大家一起思考一個(gè)問(wèn)題的掖桦。我們暫且不提他們?cè)趦?nèi)部做了多少優(yōu)化本昏,我們就說(shuō)lib的返回?cái)?shù)據(jù)。
在常用的http請(qǐng)求的返回值中枪汪,文件涌穆,JSON占絕大多數(shù)(圖片有其他框架怔昨,這里不考慮)。文件下載都有專門的response宿稀,會(huì)幫你下載到制定路徑趁舀,這個(gè)肯定都支持。那JSON呢祝沸?貌似都返回一個(gè)JSONObject或者JSONArray矮烹。
我去,做好事得做全啊罩锐,返回JSONObject是個(gè)什么鬼奉狈,難道還得自己動(dòng)手寫解析反序列化成自己要得對(duì)象?那是最低級(jí)的程序員干的事涩惑。好在我們都不傻仁期,還有GSON,fastJson竭恬,Jackson幫我們來(lái)完成這步轉(zhuǎn)化跛蛋。
比方說(shuō)服務(wù)器返回的數(shù)據(jù):(雙引號(hào)沒(méi)加,占位置萍聊,別噴)
{name:stay, age:17, job:soho}
對(duì)應(yīng)的對(duì)象:
Class User{
public String name,
public int age,
public String job
}
好问芬,那我們只需要在response回調(diào)時(shí)拿到result悦析,調(diào)用json-lib反序列化就可以了寿桨,比如這樣:
User user = gson.fromJson(result, User.class)
現(xiàn)在我們就可以使用user對(duì)象來(lái)更新UI了對(duì)吧。就多了一行代碼强戴,沒(méi)強(qiáng)迫癥的也就忍過(guò)去了亭螟。
接下來(lái)我們?cè)倏聪旅嬉环Njson數(shù)據(jù):
{resCode:200, data:{name:Stay, age:17, job:soho}, msg:success}
{resCode:401, data:{}, msg:token invalid}
我去,這是什么鬼骑歹,不好好遵守http協(xié)議预烙,統(tǒng)一返回200是什么鬼,token不合法給我返回401 error code不好嗎道媚。扁掸。別說(shuō),很多公司都這么定義返回?cái)?shù)據(jù)的
這樣我們?cè)趺崔k最域。谴分。多寫一步解析咯。
JSONObject json = new JSONObject(result)
JSONObject data = json.optJSONObject("data")
if(data != null){
User user = gson.fromJson(data.toString(), User.class)
}
天啊镀脂,即使沒(méi)強(qiáng)迫癥牺蹄,大概也會(huì)受不了每個(gè)API請(qǐng)求都寫這么多代碼了吧。
BB了這么多薄翅,大家應(yīng)該懂我想表達(dá)什么了吧沙兰?
為什么不直接將json轉(zhuǎn)換成我們要的對(duì)象User再回調(diào)呢氓奈?
而且在json數(shù)據(jù)大的情況下,反序列化還是耗時(shí)操作鼎天,有可能會(huì)卡UI的好嗎舀奶。
這可能么?當(dāng)然可以训措,不然Stay鋪墊這么多干嘛伪节。不過(guò)在Stay說(shuō)解決方案之前,大家可以試著自己考慮下實(shí)現(xiàn)绩鸣。
- 我們拿到的是String怀大,格式是JSON
- 每次拿到JSON String,我們都來(lái)做了一步反序列化對(duì)象操作
- gson.fromJson需要兩個(gè)參數(shù)(String JSON呀闻,Class dest)
- 回調(diào)參數(shù)得變成onResponse(User user)
- 框架層得知道Class dest
如果能把這些事情想清楚化借,你就可以很順利得擴(kuò)展那些開源框架了,以后你也再不用手寫json解析了捡多。
就說(shuō)這么多蓖康,留點(diǎn)時(shí)間給大家自己思考下~
最后說(shuō)下需要用到得知識(shí)點(diǎn):泛型,反射
JSON反序列化
上文中垒手,我們提到蒜焊,能否讓我們的HTTP框架幫我們完成自動(dòng)反序列化的操作。同時(shí)也給大家做了些提示:泛型和反射科贬。
現(xiàn)在我們以Volley為例:
在Volley中有三種Request:FileRequest泳梆,StringRequest,ImageRequest榜掌。
JSON數(shù)據(jù)也是字符串优妙,所以我們要重寫StringRequest中的部分方法就可以咯。
看下StringRequest源碼憎账,你會(huì)看到解析服務(wù)器byte[]到String的是parseNetworkResponse(NetworkResponse response)套硼,解析完String直接就return給外層了。
這里我們也采用相同的方式胞皱,創(chuàng)建一個(gè)GsonRequest< T >繼承Request< T >, 至于實(shí)現(xiàn)邪意,先把StringRequest的代碼copy過(guò)來(lái)。唯一不同的是反砌,StringRequest因?yàn)橹付ǚ祷豐tring類型數(shù)據(jù)所以不需要泛型雾鬼。
在parseNetworkResponse(NetworkResponse response)中,我們引入gson來(lái)反序列化json string于颖,T的class怎么辦呢呆贿?你可以通過(guò)外層顯式的傳進(jìn)來(lái)或者通過(guò)反射來(lái)拿類上的泛型T的type。兩種都可以。
具體到代碼:
擴(kuò)展完畢做入,你只需要new GsonRequest冒晰,聲明好泛型T,等待接收t對(duì)象回調(diào)就好啦竟块。
像這樣的擴(kuò)展還有很多壶运,框架不是萬(wàn)能的,要合理的根據(jù)自己的需求定制你想要的框架浪秘。
最后蒋情,留個(gè)問(wèn)題給大家,如果是服務(wù)器返回了1M的JSON數(shù)據(jù)耸携,還能用上述擴(kuò)展么棵癣?如果不可以,那該怎么辦呢夺衍?
超大JSON文本
在JSON反序列化一文的最后狈谊,有提到,如果有1M的JSON文本應(yīng)該如何來(lái)解析沟沙?
1M的JSON String河劝,不管用GSON,fastjson矛紫,jackson赎瞎,估計(jì)都要OOM了吧。本來(lái)我想說(shuō)200M的JSON數(shù)據(jù)的颊咬,想想這太坑了务甥,就改說(shuō)1M了。
答案贪染,用JsonReader讀流缓呛。比如說(shuō):
public User readUser(JsonReader reader) throws IOException {
String username = null;
int followersCount = -1;
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解析了杭隙,這太麻煩了吧。因妙。痰憎。
但是你想,跟性能比起來(lái)攀涵,這些體力也不算什么了吧铣耘。
上述沒(méi)太多特別的地方,你可以直接看JsonReader的源碼注釋以故,里面有詳細(xì)的用法示例蜗细。
在這里呢,我們先說(shuō)說(shuō)如何讓JsonReader來(lái)讀大JSON文本。
FileReader in = new FileReader(path);
JsonReader reader = new JsonReader(in);
首先炉媒,你得先把JSON文本以文件的形式存到SD卡上踪区。再通過(guò)FileReader拿到文件流,再通過(guò)JsonReader來(lái)讀流吊骤,讀流的方式也就意味著是順序讀的缎岗,所以即使它不是正確的json格式,也會(huì)一直讀到錯(cuò)誤為止白粉。
JsonReader對(duì)手寫的json解析語(yǔ)法非常嚴(yán)格传泊,寫錯(cuò)是非常頭疼的事,另外建議把nodeName變?yōu)槌A咳プ雠袛嘌及停蝗灰院蟾淖兞棵每尴埂?/p>
當(dāng)然眷细,Stay肯定不會(huì)講這么簡(jiǎn)單的東西,我們?cè)趺锤鶫TTP框架結(jié)合在一起呢鹃祖?這解析過(guò)程肯定也是耗時(shí)操作薪鹦,我總不能先用框架把數(shù)據(jù)當(dāng)文件下載下來(lái),然后再開一個(gè)線程來(lái)解析吧惯豆。這才是最蛋疼的地方池磁。
可惜原生Volley都不支持文件下載,這里我就拿自己的HTTP框架做演示了楷兽。
簡(jiǎn)單說(shuō)下實(shí)現(xiàn)過(guò)程:
首先寫個(gè)接口地熄,比如JsonReaderable,里面定義一個(gè)方法readFromJson(JsonReader reader)
-
讓你想要被反序列化的對(duì)象pojo實(shí)現(xiàn)這個(gè)接口芯杀,比如這樣這里寫
讓框架先把數(shù)據(jù)當(dāng)文件下載到SD卡
-
在callback之前再bindData端考,比如這樣這里寫
這樣就能將json數(shù)據(jù)自動(dòng)反序列化成對(duì)象callback回去了。你只需要在每個(gè)對(duì)象pojo中實(shí)現(xiàn)readFromJson方法就好了揭厚。
-
如果是jsonarray怎么辦却特,我們要返回一個(gè)ArrayList啊。比如這樣這里寫
-
一個(gè)好的框架相當(dāng)?shù)闹匾∩冈玻覀冊(cè)賮?lái)看外層的調(diào)用這里寫
應(yīng)該不用解釋吧裂明,都能看懂。
這種情況雖然比較少見(jiàn)太援,但在一些erp啊闽晦,sap項(xiàng)目中經(jīng)常會(huì)遇到(別問(wèn)Stay怎么知道)如果你也見(jiàn)過(guò)Android上500M的數(shù)據(jù)庫(kù),那這些心得你都能自己領(lǐng)悟到了提岔。
現(xiàn)在我們?cè)贏pp中基本采取的都是分頁(yè)仙蛉,一般來(lái)說(shuō)不需要用JsonReader,但如果Json數(shù)據(jù)超過(guò)10K以上碱蒙,pojo的復(fù)雜度特別高荠瘪,并且還有嵌套時(shí),你應(yīng)該考慮使用。
你也許會(huì)問(wèn)哀墓,500M鞭莽,即使用JsonReader讀流生成對(duì)象了,內(nèi)存也裝不下呀麸祷。沒(méi)事澎怒,你可以通過(guò)ormapping型數(shù)據(jù)庫(kù)框架來(lái)存數(shù)據(jù),比如說(shuō)讀200個(gè)對(duì)象存一次阶牍,清一次喷面。或者你可以用接口回調(diào)的方式扔給外層處理走孽,
onPartialDataBinding(ArrayList list)
其實(shí)這個(gè)擴(kuò)展其他第三方框架也沒(méi)什么問(wèn)題惧辈,只要思路有了,實(shí)現(xiàn)起來(lái)也就很容易了磕瓷。
框架最好是根據(jù)App具體的需求以及使用場(chǎng)景來(lái)定制盒齿,僅會(huì)調(diào)用哪些開源lib,看不懂困食,改不了边翁,這樣只能讓自己在技術(shù)路上越走越窄。
就寫到這里硕盹,別問(wèn)Stay要代碼哈符匾,只講思維與解決方案。