Retrofit响应数据及异常处理策略.docx
《Retrofit响应数据及异常处理策略.docx》由会员分享,可在线阅读,更多相关《Retrofit响应数据及异常处理策略.docx(22页珍藏版)》请在冰豆网上搜索。
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{
privateintcode;
privateStringmsg;
privateTdata;
//...set和get方法
}
Result做为所有响应模型的公共基类,其中的code,msg,data分别用来映射我们通信协议。
其中,泛型化的data确保接受不同的实体模型,可以看出,我们通过数据协议抽象之后,最终得到了一个良好的数据模型。
为了下面的需要我们一同将消息列表和消息详情的实体类放上来:
publicclassmessage{
privateStringcontent;
privateStringcreatetime;
privateStringid;
privateintstatus;
privateStringtitle;
privateStringtype;
//...set和get方法
}
publicclassmessageList{
privateinttotal;
privateListlist;
//...set和get方法
}
现在来看看我们理想的获取消息列表和获取消息详情的接口应该是什么样的:
@GET("/user/message/list")
Call>getMessageList(@Query("page")intpage);
@GET("/user/message")
Call>getMessage(@Query("mid")intmid);
结合我们上面所述,我们希望每个api最后返回给我们的都是Result
provided'com.google.code.gson:
gson:
2.7'
接下来是添加Converter依赖:
com.squareup.retrofit2:
converter-gson
最后为retrofit设置Converter:
Retrofitretrofit=newRetrofit.Builder()
.baseUrl("")
.addConverterFactory(GsonConverterFactory.create())
.build();
GitHubServiceservice=retrofit.create(GitHubService.class);
这样,我们的请求和响应由Gson进行处理:
请求体(使用@Body)被映射成json,响应体被映射成实体数据模型。
上面我们谈到了通讯协议格式以及如何利用retrofit的Converter实现协议和实体之间的自动映射。
此时我们调用任何服务接口其使用大体如下,以获取消息列表接口为例:
Call>call=ApiFactory.getUserApi().getMessageList(mCurrentPage*getPageSize(),getPageSize());
call.enqueue(newCallback>(){
@Override
publicvoidonResponse(Call>call,Response>response){
Resultresult=response.body();
if(result.isOk()){//操作正确
}else{//操作失败
switch(result.getCode()){
case1:
break;
case2:
break;
case3:
break;
case4:
break;
case5:
break;
}
}
}
@Override
publicvoidonFailure(Call>call,Throwablet){
//响应失败
}
});
错误处理
引入RxJava之前哪点事
按道理说,retrofit讲到这里已经足够了,在此基础上在进行二次封装形成自己的框架也很不错。
但是由于RxJava发展确实不错,因此retrofit引入对rxjava的支持,二者的有效结合才能发挥更强大的力量。
不了解RxJava同学可以就此打住或者先去了解相关资料。
rxjava并无多大难度,明白理论之后再加上多练即可。
对rxjava实现感兴趣的童鞋可以参看去年写的教你写响应式框架
再来说说,在新项目开始的时候,我为什么选择引入rxjava,不引入不行么?
我并未考虑引入rxjava的原因我只想使用retrofit这个网络请求库代替原有的async-http-client,后面发现引入rxjava能够非常容易的帮助我们进行线程切换以及合理的处理网络异常。
如何引入rxjava?
引入rxjava非常简单,需要添加以下依赖:
compile'io.reactivex:
rxjava:
1.1.0'
compile'io.reactivex:
rxandroid:
1.1.0'
接下来还需要引入adapter来将retrofit中Call转换为rxjava中的Observable:
compile'com.squareup.retrofit2:
adapter-rxjava:
2.0.2'
最后需要在代码中启用该adapter:
Retrofit.BuildermBuilder=new
Retrofit.Builder().addCallAdapterFactory(RxJavaCallAdapterFactory.create())
现在看引入RxJava之后接口的变化,同样还是以获取消息列表为例:
引入之前:
@GET("/user/message/list")
Call>getMessageList(@Query("start")intstart,@Query("length")intlength);
引入之后:
@GET("/user/message/list")
Observable>getMessageList(@Query("start")intstart,@Query("length")intlength);
得益于retrofit良好的设计,加入对rxjava的支持对我们接口的影响非常之小。
自定义Converter统一错误处理
我们对异常总是感觉麻烦,在客户端开发中,网络异常更是重中之重。
现在让我们回到开始,来看这段代码:
ApiFactory.getUserApi().getMessageList(0,10).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(newSubscriber>(){
@Override
publicvoidonCompleted(){
}
@Override
publicvoidonError(Throwablee){
//handlethrowable
}
@Override
publicvoidonNext(Resultresult){
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{
Tconvert(Fvalue)throwsIOException;
//用于创建Converter实例
abstractclassFactory{
//响应体转换
publicConverter>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源码:
//请求体转换
finalclassGsonRequestBodyConverterimplementsConverter{
privatestaticfinalMediaTypeMEDIA_TYPE=MediaType.parse("application/json;charset=UTF-8");
privatestaticfinalCharsetUTF_8=Charset.forName("UTF-8");
privatefinalGsongson;
privatefinalTypeAdapteradapter;
GsonRequestBodyConverter(Gsongson,TypeAdapteradapter){
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源码:
//响应体转换
finalclassGsonResponseBodyConverterimplementsConverter{
privatefinalTypeAdapteradapter;
GsonResponseBodyConverter(TypeAdapteradapter){
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;
}
@Override
publicConverter>responseBodyConverter(Typetype,Annotation[]annotations,
Retrofitretrofit){
TypeAdapter
>adapter=gson.getAdapter(TypeToken.get(type));
returnnewGsonResponseBodyConverter<>(adapter);//创建响应转换器
}
@Override
publicConverter
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:
publicclassMyGsonResponseBodyConverterimplementsConverter{
privatestaticfinalCharsetUTF_8=Charset.forName("UTF-8");
privatefinalGsonmGson;
privatefinalTypeAdapteradapter;
publicMyGsonResponseBodyConverter(Gsongson,TypeAdapteradapter){
mGson=gson;
this.adapter=adapter;
}
@Override
publicTconvert(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=