日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Retrofit响应数据及异常处理策略

發(fā)布時間:2023/12/8 编程问答 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Retrofit响应数据及异常处理策略 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

今天我們來談?wù)効蛻舳藢νㄓ崊f(xié)議的處理,主要分為三部分:約定響應(yīng)數(shù)據(jù)格式,響應(yīng)數(shù)據(jù)的自動映射以及錯誤處理三部分。由于數(shù)據(jù)協(xié)議采用json的居多,因此我們在此基礎(chǔ)上進行說明。

約定響應(yīng)數(shù)據(jù)格式

協(xié)議格式

通常來說,你拿到的設(shè)計文檔中會存在通信協(xié)議的說明,對于客戶端來說,一個良好的通信協(xié)議需要能描述操作狀態(tài)(操作碼+操作提示)以操作結(jié)果,因此,常見的響應(yīng)數(shù)據(jù)的格式如下:

{"code": 0,"msg": "正常","data": {"id": 1,"account": "121313","accountName": "alipay","income": "600.000000"} }

code定義

code為我們自定義的操作狀態(tài)碼,首先來看我們常用的定義:

code說明
0操作成功的消息提示
1客戶端認證失敗,一般是客戶端被惡意修改
2用戶認證失敗
3提交參數(shù)錯誤:參數(shù)缺失、參數(shù)名不對等
4提交參數(shù)校驗失敗,一般是提交給服務(wù)端的數(shù)據(jù)格式有誤,多發(fā)生在表單提交的場景中
5自定義錯誤,服務(wù)端發(fā)生不可恢復的錯誤等

msg定義

msg為服務(wù)器端返回的操作信息。
無論操作成功與否,客戶端都應(yīng)該根據(jù)業(yè)務(wù)給出準確的提示,客戶端則根據(jù)實際情況選擇展示與否。

data 定義

data則是請求返回的具體內(nèi)容,通常data根據(jù)請求接口的不同最終會被解析成不同的實體類。

示例

下面我們以獲取消息列表和消息詳情兩個接口返回的響應(yīng)數(shù)據(jù)作為示例:
消息列表:

{"code": 0,"data": {"list": [{"content": "你參加的活動已經(jīng)開始了...","createtime": "2016-09-23 16:44:02","id": "4480","status": 0,"title": "活動開始","type": "1"},{"content": "你參加的活動已經(jīng)結(jié)束...","createtime": "2016-09-19 14:30:02","id": "4444","status": 0,"title": "活動結(jié)束","type": "1"}],"total": 2},"msg": "正常" }

消息詳情

{"code": 0,"data": {"detail":{"content": "你參加的活動已經(jīng)開始了,請準時到你的活動中去執(zhí)行","createtime": "2016-09-23 16:44:02","id": "4480","status": 0,"title": "活動開始","type": "1"},},"msg": "正常" }

響應(yīng)數(shù)據(jù)映射實體數(shù)據(jù)模型

當我們接受到如上格式的響應(yīng)數(shù)據(jù)時,下面便是考慮如何應(yīng)用的問題,也就是如何將協(xié)議轉(zhuǎn)換?是在獲取響應(yīng)的時候自動轉(zhuǎn)換還是手動轉(zhuǎn)換?轉(zhuǎn)換成java實體類還是String?

“偷懶”是程序員的天性,我們當然不希望花費時間在這種無創(chuàng)造性的工作上,所以我們考慮在收到響應(yīng)的時候直接將其轉(zhuǎn)換為java實體類。

確定了我們的目標之后,接下來,首要任務(wù)是對數(shù)據(jù)協(xié)議進行抽象?什么叫做數(shù)據(jù)協(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做為所有響應(yīng)模型的公共基類,其中的code,msg,data分別用來映射我們通信協(xié)議。其中,泛型化的data確保接受不同的實體模型,可以看出,我們通過數(shù)據(jù)協(xié)議抽象之后,最終得到了一個良好的數(shù)據(jù)模型。

為了下面的需要我們一同將消息列表和消息詳情的實體類放上來:

public class message{private 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)在來看看我們理想的獲取消息列表和獲取消息詳情的接口應(yīng)該是什么樣的:

@GET("/user/message/list") Call<Result<MessageList>> getMessageList(@Query("page") int page);@GET("/user/message") Call<Result<Message>> getMessage(@Query("mid") int mid);

結(jié)合我們上面所述,我們希望每個api最后返回給我們的都是Result

provided 'com.google.code.gson:gson:2.7'

接下來是添加Converter依賴:

com.squareup.retrofit2:converter-gson

最后為retrofit設(shè)置Converter:

Retrofit retrofit = new Retrofit.Builder().baseUrl("https://api.github.com").addConverterFactory(GsonConverterFactory.create()).build();GitHubService service = retrofit.create(GitHubService.class);

這樣,我們的請求和響應(yīng)由Gson進行處理:請求體(使用@Body)被映射成json,響應(yīng)體被映射成實體數(shù)據(jù)模型。

上面我們談到了通訊協(xié)議格式以及如何利用retrofit的Converter實現(xiàn)協(xié)議和實體之間的自動映射。此時我們調(diào)用任何服務(wù)接口其使用大體如下,以獲取消息列表接口為例:

Call<Result<MessageList>> call = ApiFactory.getUserApi().getMessageList(mCurrentPage * getPageSize(), getPageSize());call.enqueue(new Callback<Result<MessageList>>() {@Overridepublic 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;}}}@Overridepublic void onFailure(Call<Result<MessageList>> call, Throwable t) {//響應(yīng)失敗}});

錯誤處理

引入RxJava之前哪點事

按道理說,retrofit講到這里已經(jīng)足夠了,在此基礎(chǔ)上在進行二次封裝形成自己的框架也很不錯。但是由于RxJava發(fā)展確實不錯,因此retrofit引入對rxjava的支持,二者的有效結(jié)合才能發(fā)揮更強大的力量。

不了解RxJava同學可以就此打住或者先去了解相關(guān)資料。rxjava并無多大難度,明白理論之后再加上多練即可。對rxjava實現(xiàn)感興趣的童鞋可以參看去年寫的教你寫響應(yīng)式框架

再來說說,在新項目開始的時候,我為什么選擇引入rxjava,不引入不行么?
我并未考慮引入rxjava的原因我只想使用retrofit這個網(wǎng)絡(luò)請求庫代替原有的async-http-client,后面發(fā)現(xiàn)引入rxjava能夠非常容易的幫助我們進行線程切換以及合理的處理網(wǎng)絡(luò)異常。

如何引入rxjava?

引入rxjava非常簡單,需要添加以下依賴:

compile 'io.reactivex:rxjava:1.1.0'compile 'io.reactivex:rxandroid:1.1.0'

接下來還需要引入adapter來將retrofit中Call轉(zhuǎn)換為rxjava中的Observable:

compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'

最后需要在代碼中啟用該adapter:

Retrofit.Builder mBuilder = 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("/user/message/list") Observable<Result<MessageList>> getMessageList(@Query("start") int start, @Query("length") int length);

得益于retrofit良好的設(shè)計,加入對rxjava的支持對我們接口的影響非常之小。

自定義Converter統(tǒng)一錯誤處理

我們對異??偸歉杏X麻煩,在客戶端開發(fā)中,網(wǎng)絡(luò)異常更是重中之重?,F(xiàn)在讓我們回到開始,來看這段代碼:

ApiFactory.getUserApi().getMessageList(0, 10).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<Result<MessageList>>() {@Overridepublic void onCompleted() {}@Overridepublic void onError(Throwable e) {//handle throwable}@Overridepublic void onNext(Result<MessageList> result) {if (result.isOk()) {MessageList messageList = result.getData();//handle messageList}else{int code = result.getCode();switch (code) {case 1:break;case 2:break;case 3:break;case 4:break;case 5:break;}}}});

看起很棒,我們用了rxjava中線程切換避免以往繁瑣的操作。但是好像不是那么完美:在rxjava中,所有的異常都是放在onError(),而這里的onNext()好像不是那么純粹,既要承擔正常業(yè)務(wù)邏輯還是處理異常的錯誤邏輯,換言之,onNext()干了onError()的事情,這看起來很不協(xié)調(diào)?另外,如果每個接口都要這么做,不但繁瑣而且還會長城很多重復性的代碼,長久以往,整個項目的工程質(zhì)量將無法把控。

實際上,我們希望所有的異常都是統(tǒng)一在onError()中進行處理。那么這里我們急需要明確下異常的范圍:響應(yīng)數(shù)據(jù)中code非0的情況以及其他異常。為了更好描述code非0的情況,我們定義ApiException異常類:

public class ApiException extends RuntimeException {private int errorCode;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;/** 用戶授權(quán)失敗*/int ERROR_USER_AUTHORIZED = 2;/** 請求參數(shù)錯誤*/int ERROR_REQUEST_PARAM = 3;/** 參數(shù)檢驗不通過 */int ERROR_PARAM_CHECK = 4;/** 自定義錯誤*/int ERROR_OTHER = 10;/** 無網(wǎng)絡(luò)連接*/int ERROR_NO_INTERNET = 11;}

現(xiàn)在問題就是如何將ApiException納入到rxjava的onError()當中,也就是在哪里拋出該類異常。retrofit中的Converter承擔了協(xié)議映射的功能,而ApiException只有在映射之后才能拋出,因此Converter是拋出ApiException的切入點。

先來對Converter接口有個初步的了解,其源碼如下:

public interface Converter<F, T> {T convert(F value) throws IOException;//用于創(chuàng)建Converter實例abstract class Factory {//響應(yīng)體轉(zhuǎn)換public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,Retrofit retrofit) {return null;}//請求體轉(zhuǎn)換public Converter<?, RequestBody> 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)看起.
其結(jié)構(gòu)非常簡單:GsonConverterFactory,
GsonRequestBodyConverter及GsonResponseBodyConverter,分別來看一下起源碼:

GsonRequestBodyConverter源碼:

//請求體轉(zhuǎn)換 final class GsonRequestBodyConverter<T> implements Converter<T, RequestBody> {private static final 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;GsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {this.gson = gson;this.adapter = adapter;}@Override public 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);jsonWriter.close();return RequestBody.create(MEDIA_TYPE, buffer.readByteString());} }

GsonResponseBodyConverter源碼:

//響應(yīng)體轉(zhuǎn)換 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 IOException {try {return adapter.fromJson(value.charStream());} finally {value.close();}} }

GsonConverterFactory源碼:

//轉(zhuǎn)換器 public final class GsonConverterFactory extends Converter.Factory { private final Gson gson;public static GsonConverterFactory create() {return create(new Gson());}public static GsonConverterFactory create(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 retrofit) {TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));return new GsonResponseBodyConverter<>(adapter);//創(chuàng)建響應(yīng)轉(zhuǎn)換器}@Overridepublic Converter<?, RequestBody> requestBodyConverter(Type type,Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));return new GsonRequestBodyConverter<>(gson, adapter);//創(chuàng)建請求轉(zhuǎn)換器} }

到這里我們已經(jīng)有思路了:我們需要在修改GsonResponseBodyConverter,在其中加入拋出ApiException的代碼.仿照converter-gson結(jié)構(gòu),我們自定義custom-converter-gson:

仿照GsonResponseBodyConverter編寫MyGsonResponseBodyConverter:

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, TypeAdapter<T> adapter) {mGson = gson;this.adapter = adapter;}@Overridepublic T convert(ResponseBody value) throws IOException {String response = value.string();Result re = mGson.fromJson(response, Result.class);//關(guān)注的重點,自定義響應(yīng)碼中非0的情況,一律拋出ApiException異常。//這樣,我們就成功的將該異常交給onError()去處理了。if (!re.isOk()) {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 InputStreamReader(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 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) {this.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);jsonWriter.close();return RequestBody.create(MEDIA_TYPE, buffer.readByteString());} }

仿照GsonConverterFactory編寫MyGsonConverterFactory:

public class MyGsonConverterFactory extends Converter.Factory {private final Gson gson;private MyGsonConverterFactory(Gson gson) {if (gson == null) throw new NullPointerException("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, Annotation[] annotations, Retrofit retrofit) {TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));return new STGsonResponseBodyConverter<>(gson, adapter);}@Overridepublic Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));return new STGsonRequestBodyConverter<>(gson, adapter);} }

接下來只需要在的Retrofit中使用MyGsonConverterFactory即可:

Retrofit.Builder mBuilder = new Retrofit.Builder().addConverterFactory(MyGsonConverterFactory.create())//.addConverterFactory(GsonConverterFactory.create()).addCallAdapterFactory(RxJavaCallAdapterFactory.create())

通過上面的改進,我們已經(jīng)成功的將所有異常處理點轉(zhuǎn)移至onError()當中了。這時,我們再來對比一下獲取消息列表接口的使用:

ApiFactory.getUserApi().getMessageList(0, 10).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<Result<MessageList>>() {@Overridepublic void onCompleted() {}@Overridepublic void onError(Throwable e) {if(e instanceof HttpException){//handle }else if(e instance of IOExcepton){//handle}else if(e instanceof ApiException){ApiException exception=(ApiException)e;int code = result.getErrorCode();switch (code) {case ApiErrorCode.ERROR_CLIENT_AUTHORIZED://handlebreak;case ApiErrorCode.ERROR_USER_AUTHORIZED://handlebreak;case ApiErrorCode.ERROR_REQUEST_PARAM://handlebreak;case ApiErrorCode.ERROR_PARAM_CHECK://handlebreak;case ApiErrorCode.ERROR_OTHER://handlebreak;case ApiErrorCode.ERROR_NO_INTERNET://handlebreak;}else{//handle}}@Overridepublic void onNext(Result<MessageList> result) {MessageList messageList = result.getData();//handle messageList}}});

到現(xiàn)在,已經(jīng)解決了統(tǒng)一異常處理點的問題,接下來便是要解決公共異常。不難發(fā)現(xiàn),對于大部分網(wǎng)絡(luò)異常來說,我們處理策略是相同的,因此我們希望抽取公共異常處理。除此之外,在網(wǎng)絡(luò)真正請求之前,需要對網(wǎng)絡(luò)進行判斷,無網(wǎng)絡(luò)的情況下直接拋出響應(yīng)異常。

這時候就需要自定BaseSubscriber,并在其中做相關(guān)的處理:

public class BaseSubscriber<T> extends Subscriber<T> {private Context mContext;public BaseSubscriber() {}public BaseSubscriber(Context context) {mContext = context;}@Overridepublic void onStart() {//請求開始之前,檢查是否有網(wǎng)絡(luò)。無網(wǎng)絡(luò)直接拋出異常//另外,在你無法確定當前代碼運行在什么線程的時候,不要將UI的相關(guān)操作放在這里。if (!TDevice.hasInternet()) {this.onError(new ApiException(ApiErrorCode.ERROR_NO_INTERNET, "network interrupt"));return;}}@Overridepublic void onCompleted() {}@Overridepublic void onError(Throwable e) {ApiErrorHelper.handleCommonError(mContext, e);}@Overridepublic void onNext(T t) {} } //輔助處理異常 public class ApiErrorHelper {public static void handleCommonError(Context context, Throwable e) {if (e instanceof HttpException) {Toast.makeText(context, "服務(wù)暫不可用", Toast.LENGTH_SHORT).show();} else if (e instanceof IOException) {Toast.makeText(context, "連接失敗", Toast.LENGTH_SHORT).show();} else if (e instanceof ApiException) {//ApiException處理} else {Toast.makeText(context, "未知錯誤", Toast.LENGTH_SHORT).show();}}}

現(xiàn)在再來看看獲取消息列表接口的使用

ApiFactory.getUserApi().getMessageList(mCurrentPage * getPageSize(), getPageSize()).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new BaseSubscriber<Result<MessageList>>() {@Overridepublic void onNext(Result<MessageList> result) {MessageList messageList = result.getData();//handle messageList}});

大部分接口的使用都和以上類似,針對個別異常處理只需要重寫onError()方法即可。

總結(jié)

以上是生活随笔為你收集整理的Retrofit响应数据及异常处理策略的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。