轉(zhuǎn)載自:Retrofit 解析 JSON 數(shù)據(jù) - 簡(jiǎn)書(shū)
Retro是一個(gè)類(lèi)型安全的REST客戶(hù)端,它可以直接解析JSON數(shù)據(jù)變成JAVA對(duì)象,甚至支持回調(diào)操作,處理不同的結(jié)果,本文將以IP地址API數(shù)據(jù)解析為例,講解如何使用Retrofit
本文適用于2.0以下的版本,目前1.9還是主流,此文章將漸漸成為歷史
將要使用的網(wǎng)站
文章目錄
JSON數(shù)據(jù)如何轉(zhuǎn)成JAVA
Retrofit同步獲取方法
Retrofit異步回調(diào)方法
JSON數(shù)據(jù)如何轉(zhuǎn)成JAVA
打開(kāi)了剛剛引用的API查詢(xún)網(wǎng)頁(yè),那個(gè)網(wǎng)頁(yè)給了一串JSON數(shù)據(jù)的示例
{"code":0,"data":{"ip":"210.75.225.254","country":"\u4e2d\u56fd","area":"\u534e\u5317",
"region":"\u5317\u4eac\u5e02","city":"\u5317\u4eac\u5e02","county":"","isp":"\u7535\u4fe1",
"country_id":"86","area_id":"100000","region_id":"110000","city_id":"110000",
"county_id":"-1","isp_id":"100017"}}
根據(jù)數(shù)據(jù)的大意,我們可以考慮構(gòu)建一個(gè)JavaIP類(lèi)
publicclassIP{privateintcode;privateData data;}
這個(gè)類(lèi)中的Date類(lèi)是十分麻煩的,所以我們考慮用工具直接生成
進(jìn)入JSON數(shù)據(jù)在線轉(zhuǎn)換
粘貼JSON代碼進(jìn)去,在右邊的Source Type選擇JSON,Anotation Style選擇Gson,其他的選擇自行摸索,我的配置如圖
Retrofit
注意服務(wù)器發(fā)送的區(qū)分大小寫(xiě)與帶下劃線的數(shù)據(jù)可能無(wú)法識(shí)別,導(dǎo)致返回為NULL,所以一定要勾選上Gson這個(gè)Anotation,這個(gè)勾選后,你的POJO數(shù)據(jù),以及Gson的jar包都可以完全混淆,是一種超級(jí)偷懶的寫(xiě)法。
點(diǎn)擊下方的Jar,就會(huì)生成源碼包,你可以直接扔到工程中或者改名為zip手動(dòng)折騰,注意代碼的有些注解(Annotation)可能在AS中無(wú)法通過(guò)編譯,刪除即可
HTTP GET簡(jiǎn)介
請(qǐng)求指定的頁(yè)面信息,并返回實(shí)體主體,我們可以在http鏈接中加入path,key-value等參數(shù),從而得到具體的對(duì)象。
我們回到API網(wǎng)站,它告訴了我們它的API是通過(guò)HTTP GET方法獲取的,何為GET?簡(jiǎn)單的說(shuō),就是在請(qǐng)求的url中加入key-value參數(shù),發(fā)送給服務(wù)器,這里的key是ip,value是你要查詢(xún)的地址,比如202.202.33.33
http://ip.taobao.com/service/getIpInfo.php?ip=202.202.33.33
服務(wù)器根據(jù)你的GET請(qǐng)求,返回如下的JSON數(shù)據(jù)
{"code":0, "data":{? ? "country":"中國(guó)",? ? "country_id":"CN",? ? "area":"西南",? ? "area_id":"500000",? ? "region":"重慶市",? ? "region_id":"500000",? ? "city":"重慶市",? ? "city_id":"500000",? ? "county":"",? ? "county_id":"-1",? ? "isp":"教育網(wǎng)",? ? "isp_id":"100027",? ? "ip":"202.202.33.33"}}
接下來(lái)我們?nèi)绾问褂肦etrofit獲取并解析數(shù)據(jù)呢,現(xiàn)在開(kāi)始正式的使用
Retrofit同步獲取方法
這里的同步獲取方法是指以只獲得JAVA對(duì)象為目標(biāo),而不更新UI線程中的數(shù)據(jù),異步是指獲取到數(shù)據(jù)后立刻回調(diào),更新UI線程中的界面,我們先講簡(jiǎn)單的同步,建議跟著官方Wiki一起看
打開(kāi)Android Studio,新建一個(gè)工程,添加網(wǎng)絡(luò)權(quán)限,Bulid.gradle添加如下依賴(lài)
//自行更新后面的版本號(hào)compile'com.squareup.retrofit:retrofit:1.7.1' compile'com.squareup.okhttp:okhttp-urlconnection:2.0.0' compile'com.squareup.okhttp:okhttp:2.0.0'
修改UI界面,里面放上一個(gè)EditText,一個(gè)Button,一些Textview,用于輸入和顯示UI數(shù)據(jù),這步略
寫(xiě)IP工具類(lèi),用于封裝獲取獲取IP,這里的命名IPUtils比較吐槽,各位先忽視
publicclassIPUtils{//eg : http://ip.taobao.com/service/getIpInfo.php?ip=202.202.32.202staticfinalString? ENDPOINT ="http://ip.taobao.com/service";publicinterfaceTaobaoIPService{@GET("/getIpInfo.php")IPgetIp(@Query("ip")String ip);? ? }staticRestAdapter restAdapter =newRestAdapter.Builder()? ? ? ? ? ? .setEndpoint(ENDPOINT)//是否Debug.setLogLevel(RestAdapter.LogLevel.FULL)? ? ? ? ? ? .build();staticpublicTaobaoIPService taobaoIPService = restAdapter.create(TaobaoIPService.class); }
這個(gè)IPUtils首先建立了一個(gè)接口TaobaoIPService,接著又創(chuàng)建了一個(gè)restAdapter,最后用restAdapter實(shí)例化接口,我們要獲取IP的時(shí)候,直接調(diào)用taobaoIPService中的getIp方法了,至于為什么我要寫(xiě)這個(gè)接口?官網(wǎng)上有詳細(xì)的講解
現(xiàn)在只需要在Activity中調(diào)用getIp即可
IP ip = IPUtils.taobaoIPService.getIp("202.202.33.33");
就可以獲得所有的IP數(shù)據(jù)了,注意這個(gè)任務(wù)是網(wǎng)絡(luò)任務(wù),所以不要忘記給程序加入網(wǎng)絡(luò)權(quán)限,并且讓這個(gè)getIp在非UI線程中使用(比如最簡(jiǎn)單的AsyncTask),之后如何使用IP數(shù)據(jù)就簡(jiǎn)單了,操作第二步的UI組件即可
Retrofit異步回調(diào)方法
Retrofit的異步回調(diào)是指在獲取到數(shù)據(jù)后,立刻進(jìn)行UI的更新,你不需要自己另外寫(xiě)AsyncTask,代碼看上去簡(jiǎn)潔,如果配合Dagger(一個(gè)Android注解框架,本文不討論)的使用就更加美了
建立一個(gè)工具類(lèi)
publicclassIPUtils{//eg : http://ip.taobao.com/service/getIpInfo.php?ip=202.202.32.202staticfinalString? ENDPOINT ="http://ip.taobao.com/service";publicinterfaceTaobaoIPService{@GET("/getIpInfo.php")voidgetIp(@Query("ip")String ip, Callback callback);? ? }staticRestAdapter restAdapter =newRestAdapter.Builder()? ? ? ? ? ? .setEndpoint(ENDPOINT)? ? ? ? ? ? .setLogLevel(RestAdapter.LogLevel.FULL)? ? ? ? ? ? .build();publicstaticTaobaoIPService taobaoIPService =? ? ? ? ? ? restAdapter.create(TaobaoIPService.class); }
從代碼中可以看出TaobaoIPService中的方法getIp沒(méi)有返回值了,反而多了一個(gè)Callback,官方 Wiki是這么說(shuō)的
On Android, callbacks will be executed on the main thread.
在我們結(jié)束了數(shù)據(jù)獲取后,無(wú)論是否成功,都將啟動(dòng)回調(diào),回調(diào)將在主線程(UI線程)執(zhí)行。
在Activity的使用
submitButton.setOnClickListener(newView.OnClickListener() {@OverridepublicvoidonClick(View view){? ? ? ? String query = editText.getText().toString();if(!query.isEmpty()) {//該組件能夠自動(dòng)啟動(dòng)Http線程,然后在回調(diào)中用main線程修改UI//詳情可以看“SYNCHRONOUS VS. ASYNCHRONOUS VS. OBSERVABLE”IPUtils.taobaoIPService.getIp(query,newCallback() {@Overridepublicvoidsuccess(IP ip, Response response){? ? ? ? ? ? ? ? ? ? textView_code.setText(String.valueOf(ip.getCode()));? ? ? ? ? ? ? ? ? ? textView_ip.setText(ip.getData().getIp());? ? ? ? ? ? ? ? ? ? textView_country.setText(ip.getData().getCountry());? ? ? ? ? ? ? ? ? ? textView_area.setText(ip.getData().getArea());? ? ? ? ? ? ? ? ? ? textView_region.setText(ip.getData().getRegion());? ? ? ? ? ? ? ? ? ? textView_city.setText(ip.getData().getCity());? ? ? ? ? ? ? ? ? ? textView_isp.setText(ip.getData().getIsp());? ? ? ? ? ? ? ? }@Overridepublicvoidfailure(RetrofitError error){? ? ? ? ? ? ? ? ? ? showToast("failure:"+ error.getKind());? ? ? ? ? ? ? ? }? ? ? ? ? ? });? ? ? ? }? ? } });
我們可以看出Callback重寫(xiě)了2個(gè)方法,一個(gè)是成功,一個(gè)是失敗。在成功(success)中,我們利用回調(diào)的數(shù)據(jù),直接進(jìn)行UI的更新
這里注意可能出現(xiàn)的內(nèi)存泄露,如果你是執(zhí)行耗時(shí)任務(wù),當(dāng)你退出activity后,回調(diào)后可能會(huì)出現(xiàn)空指針異常。
對(duì)錯(cuò)誤以及異常的處理
可以看到,在剛剛的代碼中,我們僅僅輸出了錯(cuò)誤的種類(lèi),沒(méi)有個(gè)性化的輸出,作為客戶(hù)端我們應(yīng)該如何處理不同的錯(cuò)誤異常呢?我們先列舉用戶(hù)出錯(cuò)的情況,常見(jiàn)的錯(cuò)誤種類(lèi)如下
publicenumKind {/** An {@link IOException} occurred while communicating to
the server. */NETWORK,/** An exception was thrown while (de)serializing a body. */CONVERSION,/** A non-200 HTTP status code was received from the server. */HTTP,/**
* An internal error occurred while attempting to execute a
request. It is best practice to
* re-throw this exception so your application crashes.
*/UNEXPECTED? ? }
NETWORK:用戶(hù)沒(méi)有聯(lián)網(wǎng),這個(gè)簡(jiǎn)單,你可以發(fā)一個(gè)Toast,或者在提交數(shù)據(jù)前檢查網(wǎng)絡(luò)連接
CONVERSION:用戶(hù)輸入錯(cuò)誤的數(shù)據(jù),比如1234,導(dǎo)致服務(wù)器返回錯(cuò)誤的數(shù)據(jù),使客戶(hù)度無(wú)法解析(CONVERSION)
{"code":1,"data":"invaild ip."}
對(duì)于這個(gè)錯(cuò)誤(CONVERSION),我們可以在本地用正則表達(dá)式在提交數(shù)據(jù)前對(duì)數(shù)據(jù)進(jìn)行簡(jiǎn)單的驗(yàn)證,當(dāng)然如果服務(wù)器真的傳來(lái)了,也沒(méi)什么,你同樣只用發(fā)一個(gè)Toast,提示“重新填寫(xiě)請(qǐng)求”即可
HTTP:處理這個(gè)錯(cuò)誤,需要服務(wù)端與客戶(hù)端寫(xiě)好技術(shù)文檔,或者使用Mock模擬所有的錯(cuò)誤,一般一個(gè)好的服務(wù)器是不會(huì)出現(xiàn)這個(gè)錯(cuò)誤的,真的出現(xiàn)了話,特例處理,比如常見(jiàn)的500,404錯(cuò)誤
UNEXPECTED:暫時(shí)沒(méi)見(jiàn)過(guò)
最后,我們寫(xiě)出的failure應(yīng)該是這樣的,健壯高效
@Overridepublicvoidfailure(RetrofitError error){switch(error.getKind()) {caseNETWORK:? ? ? showToast("網(wǎng)絡(luò)錯(cuò)誤");break;caseCONVERSION:? ? ? showToast("重新輸入");break;caseHTTP://這里可以用Mockito模擬showToast("錯(cuò)誤代碼:"+ String.valueOf(error.getResponse().getStatus())? ? ? +"錯(cuò)誤原因:"+ error.getResponse().getReason());break;caseUNEXPECTED:? ? ? showToast("未知錯(cuò)誤");//TODO:寫(xiě)入日志break;? }? showToast("failure:"+ error.getKind());}
在用界面看來(lái),用戶(hù)得到了有效的錯(cuò)誤消息,可以與開(kāi)發(fā)作者溝通反饋。
PS1:POST操作
我目前有個(gè)開(kāi)源的項(xiàng)目,圖片上傳用的就是Retrofit2.0的POST上傳,有興趣去看看吧。
PS2: Retrofit2.0
我在stackoverflow回答的關(guān)于Retrofit2.0的相關(guān)問(wèn)題
使用RxJava與Retrofit2.0使用的實(shí)例:Retrofit 2.0 RxJava Sample
JW大神的文章
http://wuxiaolong.me/2016/01/15/retrofit/
后記
本文全完,謝謝觀看!本博客持續(xù)更新與搬運(yùn)國(guó)外大神的文章,有興趣的話不妨點(diǎn)一個(gè)收藏,另外我還維護(hù)著一個(gè)材料設(shè)計(jì)的專(zhuān)題,歡迎收藏。
89145aa6a54e:@火槍輝耀了我也是最近在學(xué)習(xí)這個(gè) 緩存今天看的 你可以看下這篇文章http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0115/3873.html