Retrofit响应数据及异常处理策略_第1页
Retrofit响应数据及异常处理策略_第2页
Retrofit响应数据及异常处理策略_第3页
Retrofit响应数据及异常处理策略_第4页
Retrofit响应数据及异常处理策略_第5页
已阅读5页,还剩32页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Retrofit响应数据及异常处理策略Retrofit响应数据及异常处理策略/NUMPAGES37Retrofit响应数据及异常处理策略Retrofit响应数据及异常处理策略Retrofit响应数据及异常处理策略今天我们来谈谈客户端对通讯协议的处理,主要分为三部分:约定响应数据格式,响应数据的自动映射以及错误处理三部分。由于数据协议采用json的居多,因此我们在此基础上进行说明。约定响应数据格式协议格式通常来说,你拿到的设计文档中会存在通信协议的说明,对于客户端来说,一个良好的通信协议需要能描述操作状态(操作码+操作提示)以操作结果,因此,常见的响应数据的格式如下:{"code":0,"msg":"正常","data":{"id":1,"account":"121313","accountName":"alipay","income":"600.000000"}}code定义code为我们自定义的操作状态码,首先来看我们常用的定义:msg定义msg为服务器端返回的操作信息。无论操作成功与否,客户端都应该根据业务给出准确的提示,客户端则根据实际情况选择展示与否。data定义data则是请求返回的具体内容,通常data根据请求接口的不同最终会被解析成不同的实体类。示例下面我们以获取消息列表和消息详情两个接口返回的响应数据作为示例:消息列表:{"code":0,"data":{"list":[{"content":"你参加的活动已经开始了...","createtime":"2016-09-2316:44:02","id":"4480","status":0,"title":"活动开始","type":"1"},{"content":"你参加的活动已经结束...","createtime":"2016-09-1914:30:02","id":"4444","status":0,"title":"活动结束","type":"1"}],"total":2},"msg":"正常"}消息详情{"code":0,"data":{"detail":{"content":"你参加的活动已经开始了,请准时到你的活动中去执行","createtime":"2016-09-2316:44:02","id":"4480","status":0,"title":"活动开始","type":"1"},},"msg":"正常"}响应数据映射实体数据模型当我们接受到如上格式的响应数据时,下面便是考虑如何应用的问题,也就是如何将协议转换?是在获取响应的时候自动转换还是手动转换?转换成Java实体类还是String?“偷懒”是程序员的天性,我们当然不希望花费时间在这种无创造性的工作上,所以我们考虑在收到响应的时候直接将其转换为java实体类。确定了我们的目标之后,接下来,首要任务是对数据协议进行抽象?什么叫做数据协议抽象?所谓的数据协议抽象就是根据聚合性,通用性,隔离性三原则将整个数据协议进行切分复用,以便更好的映射成我们需要的数据模型。我们对刚才约定的数据协议格式进行协议抽象后,可以拿到类似以下的实体模型:publicclassResult<T>{privateintcode;privateStringmsg;privateTdata;//...set和get方法}Result做为所有响应模型的公共基类,其中的code,msg,data分别用来映射我们通信协议。其中,泛型化的data确保接受不同的实体模型,可以看出,我们通过数据协议抽象之后,最终得到了一个良好的数据模型。为了下面的需要我们一同将消息列表和消息详情的实体类放上来:publicclassmessage{privateStringcontent;privateStringcreatetime;privateStringid;privateintstatus;privateStringtitle;privateStringtype;//...set和get方法}publicclassmessageList{privateinttotal;privateList<Message>list;//...set和get方法}现在来看看我们理想的获取消息列表和获取消息详情的接口应该是什么样的:@GET("/user/message/list")Call<Result<MessageList>>getMessageList(@Query("page")intpage);@GET("/user/message")Call<Result<Message>>getMessage(@Query("mid")intmid);结合我们上面所述,我们希望每个api最后返回给我们的都是Result接下来是添加Converter依赖:最后为retrofit设置Converter:Retrofitretrofit=newRetrofit.Builder()m").addConverterFactory(GsonConverterFactory.create()).build();GitHubServiceservice=retrofit.create(GitHubService.class);这样,我们的请求和响应由Gson进行处理:请求体(使用@Body)被映射成json,响应体被映射成实体数据模型。上面我们谈到了通讯协议格式以及如何利用retrofit的Converter实现协议和实体之间的自动映射。此时我们调用任何服务接口其使用大体如下,以获取消息列表接口为例:Call<Result<MessageList>>call=ApiFactory.getUserApi().getMessageList(mCurrentPage*getPageSize(),getPageSize());call.enqueue(newCallback<Result<MessageList>>(){@OverridepublicvoidonResponse(Call<Result<MessageList>>call,Response<Result<MessageList>>response){Result<MessageList>result=response.body();if(result.isOk()){//操作正确}else{//操作失败switch(result.getCode()){case1:break;case2:break;case3:break;case4:break;case5:break;}}}@OverridepublicvoidonFailure(Call<Result<MessageList>>call,Throwablet){//响应失败}});错误处理引入RxJava之前哪点事按道理说,retrofit讲到这里已经足够了,在此基础上在进行二次封装形成自己的框架也很不错。但是由于RxJava发展确实不错,因此retrofit引入对rxjava的支持,二者的有效结合才能发挥更强大的力量。不了解RxJava同学可以就此打住或者先去了解相关资料。rxjava并无多大难度,明白理论之后再加上多练即可。对rxjava实现感兴趣的童鞋可以参看去年写的教你写响应式框架再来说说,在新项目开始的时候,我为什么选择引入rxjava,不引入不行么?我并未考虑引入rxjava的原因我只想使用retrofit这个网络请求库代替原有的async-http-client,后面发现引入rxjava能够非常容易的帮助我们进行线程切换以及合理的处理网络异常。如何引入rxjava?引入rxjava非常简单,需要添加以下依赖:compile'io.reactivex:rxjava:'compile'io.reactivex:rxandroid:1.1.0'接下来还需要引入adapter来将retrofit中Call转换为rxjava中的Observable:'最后需要在代码中启用该adapter:Retrofit.BuildermBuilder=newRetrofit.Builder().addCallAdapterFactory(RxJavaCallAdapterFactory.create())现在看引入RxJava之后接口的变化,同样还是以获取消息列表为例:引入之前:@GET("/user/message/list")Call<Result<MessageList>>getMessageList(@Query("start")intstart,@Query("length")intlength);引入之后:@GET("/user/message/list")Observable<Result<MessageList>>getMessageList(@Query("start")intstart,@Query("length")intlength);得益于retrofit良好的设计,加入对rxjava的支持对我们接口的影响非常之小。自定义Converter统一错误处理我们对异常总是感觉麻烦,在客户端开发中,网络异常更是重中之重。现在让我们回到开始,来看这段代码:ApiFactory.getUserApi().getMessageList(0,10).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(newSubscriber<Result<MessageList>>(){@OverridepublicvoidonCompleted(){}@OverridepublicvoidonError(Throwablee){//handlethrowable}@OverridepublicvoidonNext(Result<MessageList>result){if(result.isOk()){MessageListmessageList=result.getData();//handlemessageList}else{intcode=result.getCode();switch(code){case1:break;case2:break;case3:break;case4:break;case5:break;}}}});看起很棒,我们用了rxjava中线程切换避免以往繁琐的操作。但是好像不是那么完美:在rxjava中,所有的异常都是放在onError(),而这里的onNext()好像不是那么纯粹,既要承担正常业务逻辑还是处理异常的错误逻辑,换言之,onNext()干了onError()的事情,这看起来很不协调?另外,如果每个接口都要这么做,不但繁琐而且还会长城很多重复性的代码,长久以往,整个项目的工程质量将无法把控。实际上,我们希望所有的异常都是统一在onError()中进行处理。那么这里我们急需要明确下异常的范围:响应数据中code非0的情况以及其他异常。为了更好描述code非0的情况,我们定义ApiException异常类:publicclassApiExceptionextendsRuntimeException{privateinterrorCode;publicApiException(intcode,Stringmsg){super(msg);this.errorCode=code;}publicintgetErrorCode(){returnerrorCode;}}另外为了更好描述code,我们也将其定义成ApiErrorCode:publicinterfaceApiErrorCode{/**客户端错误*/intERROR_CLIENT_AUTHORIZED=1;/**用户授权失败*/intERROR_USER_AUTHORIZED=2;/**请求参数错误*/intERROR_REQUEST_PARAM=3;/**参数检验不通过*/intERROR_PARAM_CHECK=4;/**自定义错误*/intERROR_OTHER=10;/**无网络连接*/intERROR_NO_INTERNET=11;}现在问题就是如何将ApiException纳入到rxjava的onError()当中,也就是在哪里抛出该类异常。retrofit中的Converter承担了协议映射的功能,而ApiException只有在映射之后才能抛出,因此Converter是抛出ApiException的切入点。先来对Converter接口有个初步的了解,其源码如下:publicinterfaceConverter<F,T>{Tconvert(Fvalue)throwsIOException;//用于创建Converter实例abstractclassFactory{//响应体转换publicConverter<ResponseBody,?>responseBodyConverter(Typetype,Annotation[]annotations,Retrofitretrofit){returnnull;}//请求体转换publicConverter<?,RequestBody>requestBodyConverter(Typetype,Annotation[]parameterAnnotations,Annotation[]methodAnnotations,Retrofitretrofit){returnnull;}publicConverter<?,String>stringConverter(Typetype,Annotation[]annotations,Retrofitretrofit){returnnull;}}}接下来,我们从retrofit提供的converter-gson的实现看起.其结构非常简单:GsonConverterFactory,GsonRequestBodyConverter及GsonResponseBodyConverter,分别来看一下起源码:GsonRequestBodyConverter源码://请求体转换finalclassGsonRequestBodyConverter<T>implementsConverter<T,RequestBody>{privatestaticfinalMediaTypeMEDIA_TYPE=MediaType.parse("application/json;charset=UTF-8");privatestaticfinalCharsetUTF_8=Charset.forName("UTF-8");privatefinalGsongson;privatefinalTypeAdapter<T>adapter;GsonRequestBodyConverter(Gsongson,TypeAdapter<T>adapter){this.gson=gson;this.adapter=adapter;}@OverridepublicRequestBodyconvert(Tvalue)throwsIOException{Bufferbuffer=newBuffer();Writerwriter=newOutputStreamWriter(buffer.outputStream(),UTF_8);JsonWriterjsonWriter=gson.newJsonWriter(writer);adapter.write(jsonWriter,value);jsonWriter.close();returnRequestBody.create(MEDIA_TYPE,buffer.readByteString());}}GsonResponseBodyConverter源码://响应体转换finalclassGsonResponseBodyConverter<T>implementsConverter<ResponseBody,T>{privatefinalTypeAdapter<T>adapter;GsonResponseBodyConverter(TypeAdapter<T>adapter){this.adapter=adapter;}@OverridepublicTconvert(ResponseBodyvalue)throwsIOException{try{returnadapter.fromJson(value.charStream());}finally{value.close();}}}GsonConverterFactory源码://转换器publicfinalclassGsonConverterFactoryextendsConverter.Factory{privatefinalGsongson;publicstaticGsonConverterFactorycreate(){returncreate(newGson());}publicstaticGsonConverterFactorycreate(Gsongson){returnnewGsonConverterFactory(gson);}privateGsonConverterFactory(Gsongson){if(gson==null)thrownewNullPointerException("gson==null");this.gson=gson;}@OverridepublicConverter<ResponseBody,?>responseBodyConverter(Typetype,Annotation[]annotations,Retrofitretrofit){TypeAdapter<?>adapter=gson.getAdapter(TypeToken.get(type));returnnewGsonResponseBodyConverter<>(adapter);//创建响应转换器}@OverridepublicConverter<?,RequestBody>requestBodyConverter(Typetype,Annotation[]parameterAnnotations,Annotation[]methodAnnotations,Retrofitretrofit){TypeAdapter<?>adapter=gson.getAdapter(TypeToken.get(type));returnnewGsonRequestBodyConverter<>(gson,adapter);//创建请求转换器}}到这里我们已经有思路了:我们需要在修改GsonResponseBodyConverter,在其中加入抛出ApiException的代码.仿照converter-gson结构,我们自定义custom-converter-gson:仿照GsonResponseBodyConverter编写MyGsonResponseBodyConverter:publicclassMyGsonResponseBodyConverter<T>implementsConverter<ResponseBody,T>{privatestaticfinalCharsetUTF_8=Charset.forName("UTF-8");privatefinalGsonmGson;privatefinalTypeAdapter<T>adapter;publicMyGsonResponseBodyConverter(Gsongson,TypeAdapter<T>adapter){mGson=gson;this.adapter=adapter;}@OverridepublicTconvert(ResponseBodyvalue)throwsIOException{Stringresponse=value.string();Resultre=mGson.fromJson(response,Result.class);//关注的重点,自定义响应码中非0的情况,一律抛出ApiException异常。//这样,我们就成功的将该异常交给onError()去处理了。if(!re.isOk()){value.close();thrownewApiException(re.getCode(),re.getMsg());}MediaTypemediaType=value.contentType();Charsetcharset=mediaType!=null?mediaType.charset(UTF_8):UTF_8;ByteArrayInputStreambis=newByteArrayInputStream(response.getBytes());InputStreamReaderreader=newInputStreamReader(bis,charset);JsonReaderjsonReader=mGson.newJsonReader(reader);try{returnadapter.read(jsonReader);}finally{value.close();}}}仿照GsonRequestBodyConverter编写MyGsonRequestBodyConverter:publicclassMyGsonRequestBodyConverter<T>implementsConverter<T,RequestBody>{privatestaticfinalMediaTypeMEDIA_TYPE=MediaType.parse("application/json;charset=UTF-8");privatestaticfinalCharsetUTF_8=Charset.forName("UTF-8");privatefinalGsongson;privatefinalTypeAdapter<T>adapter;publicMyGsonRequestBodyConverter(Gsongson,TypeAdapter<T>adapter){this.gson=gson;this.adapter=adapter;}@OverridepublicRequestBodyconvert(Tvalue)throwsIOException{Bufferbuffer=newBuffer();Writerwriter=newOutputStreamWriter(buffer.outputStream(),UTF_8);JsonWriterjsonWriter=gson.newJsonWriter(writer);adapter.write(jsonWriter,value);jsonWriter.close();returnRequestBody.create(MEDIA_TYPE,buffer.readByteString());}}仿照GsonConverterFactory编写MyGsonConverterFactory:publicclassMyGsonConverterFactoryextendsConverter.Factory{privatefinalGsongson;privateMyGsonConverterFactory(Gsongson){if(gson==null)thrownewNullPointerException("gson==null");this.gson=gson;}publicstaticMyGsonConverterFactorycreate(){returncreate(newGson());}publicstaticMyGsonConverterFactorycreate(Gsongson){returnnewMyGsonConverterFactory(gson);}@OverridepublicConverter<ResponseBody,?>responseBodyConverter(Typetype,Annotation[]annotations,Retrofitretrofit){TypeAdapter<?>adapter=gson.getAdapter(TypeToken.get(type));returnnewSTGsonResponseBodyConverter<>(gson,adapter);}@OverridepublicConverter<?,RequestBody>requestBodyConverter(Typetype,Annotation[]parameterAnnotations,Annotation[]methodAnnotations,Retrofitretrofit){TypeAdapter<?>adapter=gson.getAdapter(TypeToken.get(type));returnnewSTGsonRequestBodyConverter<>(gson,adapter);}}接下来只需要在的Retrofit中使用MyGsonConverterFactory即可:Retrofit.BuildermBuilder=newRetrofit.Builder().addConverterFactory(MyGsonConverterFactory.create())//.addConverterFactory(GsonConverterFactory.create()).addCallAdapterFactory(RxJavaCallAdapterFactory.create())通过上面的改进,我们已经成功的将所有异常处理点转移至onError()当中了。这时,我们再来对比一下获取消息列表接口的使用:ApiFactory.getUserApi().getMessageList(0,10).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(newSubscriber<Result<MessageList>>(){@OverridepublicvoidonCompleted(){}@OverridepublicvoidonError(Throwablee){if(einstanceofHttpException){//handle}elseif(einstanceofIOExcepton){//handle}elseif(einstanceofApiException){ApiExceptionexception=(ApiException)e;intcode=result.getErrorCode();switch(code){caseApiErrorCode.ERROR_CLIENT_AUTHORIZED://handlebreak;caseApiErrorCode.ERROR_USER_AUTHORIZED://handlebreak;caseApiErrorCode.ERROR_REQUEST_PARAM://handlebreak;caseApiErrorCode.ERROR_PARAM_CHECK://handlebreak;caseApiErrorCode.ERROR_OTHER://handlebreak;caseApiErrorCode.ERROR_NO_INTERNET://handlebreak;}else{//handle}}@OverridepublicvoidonNext(Result<MessageList>result){MessageListmessageList=result.getData();//handlemessageList}}});到现在,已经解决了统一异常处理点的问题,接下来便是要解决公共异常。不难发现,对于大部分网络异常来说,我们处理策略是相同的,因此我们希望抽取公共异常处理。除此之外,在网络真正请求之前,需要对网络进行判断,无网络的情况

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论