版權說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權,請進行舉報或認領
文檔簡介
1、Retrofit響應數(shù)據(jù)及異常處理策略今天我們來談談客戶端對通訊協(xié)議的處理,主要分為三部分:約定響應數(shù)據(jù)格式,響應數(shù)據(jù)的自動映射以及錯誤處理三部分。由于數(shù)據(jù)協(xié)議采用json的居多,因此我們在此基礎上進行說明。約定響應數(shù)據(jù)格式 協(xié)議格式通常來說,你拿到的設計文檔中會存在通信協(xié)議的說明,對于客戶端來說,一個良好的通信協(xié)議需要能描述操作狀態(tài)(操作碼 +操作提示)以操作結果,因此,常見的響應數(shù)據(jù)的格式 如下:"code": 0,"msg":"正常","data": "id": 1,"accou
2、 nt": "121313", "acco un tName": "alipay", "in come": "600.000000"code定義code為我們自定義的操作狀態(tài)碼,首先來看我們常用的定義:code說圈0噪作成功的消息提示1容戶端認證失毆-一般是客戶常襪惡意修改2用尸”一證失敗3桿交琵數(shù)錯謠:琴觀缺去、菴數(shù)若不對等411交塑數(shù)校髓軼敗,一股是提交綸服芻誦的數(shù)據(jù)林式有俁,實接生正表単11交的場員中5自定義醐 > 展務靖發(fā)主不可恢復的錯誤等msg定義msg為服務器端返回的
3、操作信息。無論操作成功與否,客戶端都應該根據(jù)業(yè)務給出準確的提示,客戶端則根據(jù)實際情況選擇展示與否。data定義示例 下面我們以獲取消息列表和消息詳情兩個接口返回的響應數(shù)據(jù)作為示例: 消息列表:"code": 0,"data": "list": "content": " 你參加的活動已經(jīng)開始了.","createtime": "2016-09-23 16:44:02", "id": "4480","statu
4、s": 0,"title": " 活動開始 ", "type": "1", "content": " 你參加的活動已經(jīng)結束.","createtime": "2016-09-19 14:30:02", "id": "4444","status": 0,"title": " 活動結束 ", "type": &q
5、uot;1","total": 2,"msg": " 正常 "消息詳情"code": 0,"data": "detail":"content": " 你參加的活動已經(jīng)開始了, 請準時到你的活動中去執(zhí)行"createtime": "2016-09-23 16:44:02","id": "4480", "status": 0,"ti
6、tle": " 活動開始 ","type": "1","msg": " 正常 "響應數(shù)據(jù)映射實體數(shù)據(jù)模型當我們接受到如上格式的響應數(shù)據(jù)時, 下面便是考慮如何應用的問題, 也就是如何將協(xié)議轉 換?是在獲取響應的時候自動轉換還是手動轉換?轉換成 Java 實體類還是 String?“偷懶” 是程序員的天性, 我們當然不希望花費時間在這種無創(chuàng)造性的工作上, 所以我們考 慮在收到響應的時候直接將其轉換為 java 實體類。確定了我們的目標之后,接下來,首要任務是對數(shù)據(jù)協(xié)議進行抽象?什么叫做數(shù)據(jù)
7、協(xié)議抽 象?所謂的數(shù)據(jù)協(xié)議抽象就是根據(jù)聚合性, 通用性,隔離性三原則將整個數(shù)據(jù)協(xié)議進行切分復用, 以便更好的映射成我們需要的數(shù)據(jù)模型。我們對剛才約定的數(shù)據(jù)協(xié)議格式進行協(xié)議抽象后,可以拿到類似以下的實體模型:public class Result<T> private int code; private String msg;private T data;/.set 和 get 方法Result 做為所有響應模型的公共基類, 其中的 code,msg,data 分別用來映射我們通信協(xié)議。 其中,泛型化的 data 確保接受不同的實體模型,可以看出,我們通過數(shù)據(jù)協(xié)議抽象之后, 最終得到
8、了一個良好的數(shù)據(jù)模型。為了下面的需要我們一同將消息列表和消息詳情的實體類放上來:public class messageprivate String content;private String createtime;private String id;private int status;private String title;private String type;/.set 和 get 方法public class messageList private int total; private List<Message> list;/.set 和 get 方法現(xiàn)在來看看我們理
9、想的獲取消息列表和獲取消息詳情的接口應該是什么樣的:GET("/user/message/list")Call<Result<MessageList>> getMessageList(Query("page") int page);GET("/user/message")Call<Result<Message>> getMessage(Query("mid") int mid);結合我們上面所述,我們希望每個 api 最后返回給我們的都是 Result接下來是添加 C
10、onverter 依賴:.squareup.retrofit2:converter-gson最后為 retrofit 設置 Converter :Retrofit retrofit = new Retrofit.Builder() .baseUrl(".") .addConverterFactory(GsonConverterFactory.create() .build();GitHubService = retrofit.create(GitHubService.class);這樣,我們的請求和響應由Gson進行處理:請求體(使用Body)被映射成json,響應體被映射
11、成實體數(shù)據(jù)模型。上面我們談到了通訊協(xié)議格式以及如何利用 retrofit 的 Converter 實現(xiàn)協(xié)議和實體之間的自動映射。此時我們調(diào)用任何服務接口其使用大體如下,以獲取消息列表接口為例:Call<Result<MessageList>> call = ApiFactory.getUserApi().getMessageList(mCurrentPage * getPageSize(), getPageSize();call.enqueue(new Callback<Result<MessageList>>() Overridepublic
12、void onResponse(Call<Result<MessageList>> call, Response<Result<MessageList>> response) Result<MessageList> result = response.body();if (result.isOk() / 操作正確 else / 操作失敗switch (result.getCode() case 1:break;case 2:break;case 3:break;case 4:break;case 5:break;Overridepub
13、lic void onFailure(Call<Result<MessageList>> call, Throwable t) /響應失敗);錯誤處理引入 RxJava 之前哪點事按道理說, retrofit 講到這里已經(jīng)足夠了, 在此基礎上在進行二次封裝形成自己的框架也很不 錯。但是由于 RxJava 發(fā)展確實不錯,因此 retrofit 引入對 rxjava 的支持,二者的有效結合才 能發(fā)揮更強大的力量。不了解 RxJava 同學可以就此打住或者先去了解相關資料。 rxjava 并無多大難度,明白理論 之后再加上多練即可。對 rxjava 實現(xiàn)感興趣的童鞋可以參看去
14、年寫的教你寫響應式框架再來說說,在新項目開始的時候,我為什么選擇引入rxjava ,不引入不行么?我并未考慮引入 rxjava 的原 因我 只想使 用 retrofit 這個網(wǎng) 絡請求 庫代替原有 的 async-http-client ,后面發(fā)現(xiàn)引入 rxjava 能夠非常容易的幫助我們進行線程切換以及合理的處 理網(wǎng)絡異常。如何引入 rxjava ?引入 rxjava 非常簡單,需要添加以下依賴:接下來還需要引入 adapter 來將 retrofit 中 Call 轉換為 rxjava 中的 Observable:最后需要在代碼中啟用該 adapter:Retrofit.Builder m
15、Builder = new Retrofit.Builder().addCallAdapterFactory(RxJavaCallAdapterFactory.create()現(xiàn)在看引入 RxJava 之后接口的變化,同樣還是以獲取消息列表為例: 引入之前:GET("/user/message/list") Call<Result<MessageList>> getMessageList(Query("start") int start, Query("length") int length);引入之后:GET
16、("/user/message/list")Observable<Result<MessageList>> getMessageList(Query("start") int start, Query("length") int length);得益于 retrofit 良好的設計,加入對 rxjava 的支持對我們接口的影響非常之小。自定義 Converter 統(tǒng)一錯誤處理我們對異??偸歉杏X麻煩, 在客戶端開發(fā)中, 網(wǎng)絡異常更是重中之重。 現(xiàn)在讓我們回到開始, 來看這段代碼:ApiFactory.getUse
17、rApi().getMessageList(0, 10).subscribeOn(Schedulers.io() .observeOn(AndroidSchedulers.mainThread() .subscribe(new Subscriber<Result<MessageList>>() Overridepublic void onCompleted() Override public void onError(Throwable e) /handle throwableOverridepublic void onNext(Result<MessageLis
18、t> result) if (result.isOk() MessageList messageList = result.getData();/handle messageListelseint code = result.getCode(); switch (code) case 1:break; case 2:break;case 3:break;case 4:break;case 5:break; );看起很棒,我們用了 rxjava 中線程切換避免以往繁瑣的操作。但是好像不是那么完美:在 rxjava中,所有的異常都是放在 onError(),而這里的onNext()好像不是那
19、么純粹,既要承擔正常業(yè)務邏輯還是處理異常的錯誤邏輯,換言之,onNext()干了 onError()的事情,這看起來很不協(xié)調(diào)?另外, 如果每個接口都要這么做, 不但繁瑣而且還會長城很多重復性的代碼, 長久 以往,整個項目的工程質(zhì)量將無法把控。實際上,我們希望所有的異常都是統(tǒng)一在on Error ()中進行處理。那么這里我們急需要明確下異常的圍:響應數(shù)據(jù)中code非0的情況以及其他異常。為了更好描述code非0的情況,我們定義 ApiException 異常類:public class ApiException extends RuntimeException private int error
20、Code;public ApiException(int code, String msg) super(msg);this.errorCode = code;public int getErrorCode() return errorCode;另外為了更好描述 code,我們也將其定義成 ApiErrorCode:public interface ApiErrorCode /* 客戶端錯誤 */int ERROR_CLIENT_AUTHORIZED = 1;/* 用戶授權失敗 */int ERROR_USER_AUTHORIZED = 2;/* 請求參數(shù)錯誤 */int ERROR_REQU
21、EST_PARAM = 3;/* 參數(shù)檢驗不通過 */int ERROR_PARAM_CHECK = 4;/* 自定義錯誤 */int ERROR_OTHER = 10;/* 無網(wǎng)絡連接 */int ERROR_NO_INTERNET = 11;()當中,也就是在哪里拋出該ApiException 只有在映射之后才現(xiàn)在問題就是如何將 ApiException 納入到 rxjava 的 onError 類異常。 retrofit 中的 Converter 承擔了協(xié)議映射的功能,而 能拋出,因此 Converter 是拋出 ApiException 的切入點。先來對 Converter 接口有個初
22、步的了解,其源碼如下:public interface Converter<F, T> T convert(F value) throws IOException;/ 用于創(chuàng)建 Converter 實例 abstract class Factory /響應體轉換public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation annotations,Retrofit retrofit) return null;/請求體轉換public Converter<?, Request
23、Body> requestBodyConverter(Type type,Annotation parameterAnnotations, Annotation methodAnnotations, Retrofit retrofit) return null;public Converter<?, String> stringConverter(Type type, Annotation annotations, Retrofit retrofit) return null;接下來 ,我們從 retrofit 提供的 converter-gson 的實現(xiàn)看起 . 其結構非常
24、簡單: GsonConverterFactory , GsonRequestBodyConverter 及 GsonResponseBodyConverter ,分別來看一下起源碼:GsonRequestBodyConverter 源碼 :/請求體轉換final class GsonRequestBodyConverter<T> implements Converter<T, RequestBody> private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; c
25、harset=UTF-8");private static final Charset UTF_8 = Charset.forName("UTF-8");private final Gson gson;private final TypeAdapter<T> adapter;GsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) this.gson = gson;this.adapter = adapter;Override public RequestBody convert
26、(T value) throws IOException Buffer buffer = new Buffer();Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);JsonWriter jsonWriter = gson.newJsonWriter(writer); adapter.write(jsonWriter, value);jsonWriter.close();return RequestBody.create(MEDIA_TYPE, buffer.readByteString();GsonRes
27、ponseBodyConverter 源碼 :/響應體轉換final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> private final TypeAdapter<T> adapter;GsonResponseBodyConverter(TypeAdapter<T> adapter) this.adapter = adapter;Override public T convert(ResponseBody value) throws IOExc
28、eption try return adapter.fromJson(value.charStream(); finally value.close();GsonConverterFactory 源碼:/轉換器public final class GsonConverterFactory extends Converter.Factory private final Gson gson;public static GsonConverterFactory create() return create(new Gson();public static GsonConverterFactory c
29、reate(Gson gson) return new GsonConverterFactory(gson);private GsonConverterFactory(Gson gson) if (gson = null) throw new NullPointerException("gson = null"); this.gson = gson;Overridepublic Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation annotations,Retrofit r
30、etrofit) TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type); return new GsonResponseBodyConverter<>(adapter);/ 創(chuàng)建響應轉換器Overridepublic Converter<?, RequestBody> requestBodyConverter(Type type,Annotation parameterAnnotations, Annotation methodAnnotations, Retrofit retrofit)
31、TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type); return new GsonRequestBodyConverter<>(gson, adapter);/ 創(chuàng)建請求轉換器到這里我們已經(jīng)有思路了:我們需要在修改 GsonResponseBodyConverter ,在其中加入拋出 ApiException 的代碼 . 仿照 converter-gson 結構,我們自定義 custom-converter-gson:仿照 GsonResponseBodyConverter 編寫 MyGsonRes
32、ponseBodyConverter :public class MyGsonResponseBodyConverter<T> implements Converter<ResponseBody, T> private static final Charset UTF_8 = Charset.forName("UTF-8");private final Gson mGson;private final TypeAdapter<T> adapter;public MyGsonResponseBodyConverter(Gson gson,
33、TypeAdapter<T> adapter) mGson = gson;this.adapter = adapter;Override public T convert(ResponseBody value) throws IOException String response = value.string();Result re = mGson.fromJson(response, Result.class); /關注的重點,自定義響應碼中非0 的情況,一律拋出 ApiException 異常。/這樣,我們就成功的將該異常交給onError() 去處理了。if (!re.isO
34、k() value.close();throw new ApiException(re.getCode(), re.getMsg();MediaType mediaType = value.contentType();Charset charset = mediaType != null ? mediaType.charset(UTF_8) : UTF_8; ByteArrayInputStream bis = new ByteArrayInputStream(response.getBytes(); InputStreamReader reader = new InputStreamRead
35、er(bis,charset); JsonReader jsonReader = mGson.newJsonReader(reader);try return adapter.read(jsonReader); finally value.close();仿照 GsonRequestBodyConverter 編寫 MyGsonRequestBodyConverter :public class MyGsonRequestBodyConverter<T> implements Converter<T, RequestBody> private static final
36、MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");private static final Charset UTF_8 = Charset.forName("UTF-8");private final Gson gson;private final TypeAdapter<T> adapter;public MyGsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) th
37、is.gson = gson;this.adapter = adapter;Overridepublic RequestBody convert(T value) throws IOException Buffer buffer = new Buffer();Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);JsonWriter jsonWriter = gson.newJsonWriter(writer); adapter.write(jsonWriter, value);return RequestBo
38、dy.create(MEDIA_TYPE, buffer.readByteString();AnnotationAnnotation這時,我仿照 GsonConverterFactory 編寫 MyGsonConverterFactory :public class MyGsonConverterFactory extends Converter.Factory private final Gson gson;private MyGsonConverterFactory(Gson gson) if (gson = null) throw new NullPointerException(&qu
39、ot;gson = null"); this.gson = gson;public static MyGsonConverterFactory create() return create(new Gson();public static MyGsonConverterFactory create(Gson gson) return new MyGsonConverterFactory(gson);Overridepublic Converter<ResponseBody, ?> responseBodyConverter(Type type, annotations,
40、Retrofit retrofit) TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type); return new STGsonResponseBodyConverter<>(gson, adapter);Overridepublic Converter<?, RequestBody> requestBodyConverter(Type type, parameterAnnotations, Annotation methodAnnotations, Retrofit retrofit) T
41、ypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type); return new STGsonRequestBodyConverter<>(gson, adapter);接下來只需要在的 Retrofit 中使用 MyGsonConverterFactory 即可:Retrofit.Builder mBuilder = new Retrofit.Builder().addConverterFactory(MyGsonConverterFactory.create()/.addConverterFactory(
42、GsonConverterFactory.create() .addCallAdapterFactory(RxJavaCallAdapterFactory.create()通過上面的改進,我們已經(jīng)成功的將所有異常處理點轉移至onError ()當中了們再來對比一下獲取消息列表接口的使用:ApiFactory.getUserApi().getMessageList(0, 10).subscribeOn(Schedulers.io().observeOn(AndroidSchedulers.mainThread().subscribe(new Subscriber<Result<Mes
43、sageList>>() Overridepublic void onCompleted() Overridepublic void onError(Throwable e) if(e instanceof HttpException)/handleelse if(e instance of IOExcepton)/handleelse if(e instanceof ApiException)ApiException exception=(ApiException)e;int code = result.getErrorCode();switch (code) case ApiE
44、rrorCode.ERROR_CLIENT_AUTHORIZED:/handle break;case ApiErrorCode.ERROR_USER_AUTHORIZED:/handle break;case ApiErrorCode.ERROR_REQUEST_PARAM:/handlebreak;case ApiErrorCode.ERROR_PARAM_CHECK:/handle break;case ApiErrorCode.ERROR_OTHER:/handle break;case ApiErrorCode.ERROR_NO_INTERNET:/handlebreak;else/handleOverridepublic void onNext(Result<MessageList> result) MessageList messageList = result.getData();/handle messageList);到現(xiàn)在,已經(jīng)解決了統(tǒng)一異常處理點的問題, 接下來便是要解決公共異常。 不難發(fā)現(xiàn), 對于 大部分網(wǎng)絡異常來說, 我們處理策略是相
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 餐車買賣合同范本
- 北京市科技 技術開發(fā)合同模板 申請免稅
- 重慶市第九十四中學校2024-2025學年高二上學期期中考試英語試題(含答案無聽力原文及音頻)
- 柳州市2025屆高三第一次模擬考試(一模)數(shù)學試卷(含答案)
- 湖北省武漢市江夏實驗高級中學2024-2025學年高三上學期11月模擬歷史試題(含答案)
- 廣東省深圳高級中學北校區(qū)等多校2024-2025學年七年級上學期期中生物學試題(含答案)
- 郵政專用機械及器材相關行業(yè)投資方案
- 環(huán)保特種電線電纜相關行業(yè)投資方案范本
- 民宿旅游相關行業(yè)投資規(guī)劃報告范本
- 溫控儀表相關項目投資計劃書范本
- 電子琴伴奏及音色中英文對照表
- 蘇教版初中化學常見氣體的檢驗與除雜教案
- 網(wǎng)絡教研——開辟校本教研新模式
- 火災報警系統(tǒng)技術規(guī)范書
- 魚塘租賃合同
- 教材自編傳統(tǒng)節(jié)日校本課程
- 樓宇自控系統(tǒng)調(diào)試方案
- 排水管道施工方案(完整版)
- hydac壓力繼電器說明書
- 中成藥上市公司組織架構及部門職責
- 《教育學原理》課程教學大綱
評論
0/150
提交評論