目录介绍
- 01.网络请求异常分类
- 02.开发中注意问题
- 03.原始的处理方式
- 04.如何减少代码耦合性
- 05.异常统一处理步骤
- 06.完成版代码展示
好消息
- 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢!
- 链接地址:https://github.com/yangchong2...
- 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!
01.网络请求异常分类
-
网络请求异常大概有哪些?
- 第一种:访问接口异常,比如404,500等异常,出现这类异常,Retrofit会自动抛出异常。
- 第二种:解析数据异常,数据体发生变化可能会导致这个问题。
- 第三种:其他类型异常,比如服务器响应超时异常,链接失败异常,网络未连接异常等等。
- 第四种:网络请求成功,但是服务器定义了异常状态,比如token失效,参数传递错误,或者统一给提示(这个地方比较拗口,比如购物app,你购买n件商品请求接口成功,code为200,但是服务器发现没有这么多商品,这个时候就会给你一个提示,然后客户端拿到这个进行吐司)
02.开发中注意问题
-
在获取数据的流程中,访问接口和解析数据时都有可能会出错,我们可以通过拦截器在这两层拦截错误。
- 1.在访问接口时,我们不用设置拦截器,因为一旦出现错误,Retrofit会自动抛出异常。比如,常见请求异常404,500,503等等。为了方便后期排查问题,这个可以在debug环境下打印日志就可以。
- 2.在解析数据时,我们设置一个拦截器,判断Result里面的code是否为成功,如果不成功,则要根据与服务器约定好的错误码来抛出对应的异常。比如,token失效后跳转登录页面,禁用同账号登陆多台设备,缺少参数,参数传递异常等等。
- 3.除此以外,为了我们要尽量避免在View层对错误进行判断,处理,我们必须还要设置一个拦截器,拦截onError事件,然后使用ExceptionUtils,让其根据错误类型来分别处理。
03.原始的处理方式
-
最简单的处理方式,直接对返回的throwable进行类型判断处理
//请求,对throwable进行判断 ServiceHelper.getInstance() .getModelResult(param1, param2) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<Model>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { if(e instanceof HttpException){ //获取对应statusCode和Message HttpException exception = (HttpException)e; String message = exception.response().message(); int code = exception.response().code(); }else if(e instanceof SSLHandshakeException){ //接下来就是各种异常类型判断... }else if(e instanceof ...){ }... } @Override public void onNext(Model model) { if(model.getCode != CODE_SUCCESS){ int code = model.getCode(); switch (code){ case CODE_TOKEN_INVALID: ex.setDisplayMessage("重新登陆"); break; case CODE_NO_OTHER: ex.setDisplayMessage("其他情况"); break; case CODE_SHOW_TOAST: ex.setDisplayMessage("吐司服务器返回的提示"); break; case CODE_NO_MISSING_PARAMETER: ex.setDisplayMessage("缺少参数,用log记录服务器提示"); break; default: ex.setDisplayMessage(message); break; } }else{ //正常处理逻辑 } } });
04.如何减少代码耦合性
-
为了不改变以前的代码结构,那么如何做才能够彻底解耦呢?一般情况下使用Retrofit网络请求框架,会有回调方法,如下所示:
package retrofit2; public interface Callback<T> { void onResponse(Call<T> var1, Response<T> var2); void onFailure(Call<T> var1, Throwable var2); }
-
不管以前代码封装与否,都希望一句代码即可实现网络请求拦截处理逻辑。那么这个时候,我是怎么处理的呢?
public class ResponseData<T> { private int code; private String message; private T t; public int getCode() { return code; } public String getMessage() { return message; } public T getT() { return t; } } new Callback<ResponseData<HomeBlogEntity>>(){ @Override public void onResponse(Call<ResponseData<HomeBlogEntity>> call, Response<ResponseData<HomeBlogEntity>> response) { int code = response.body().getCode(); String message = response.body().getMessage(); HomeBlogEntity t = response.body().getT(); if (code!= CODE_SUCCESS){ //网络请求成功200,不过业务层执行服务端制定的异常逻辑 ExceptionUtils.serviceException(code,message); } else { //网络请求成功,业务逻辑正常处理 } } @Override public void onFailure(Call call, Throwable throwable) { ExceptionUtils.handleException(throwable); } };
05.异常统一处理步骤
-
第一步:定义请求接口网络层失败的状态码
/**
*/
private static final int BAD_REQUEST = 400;
private static final int UNAUTHORIZED = 401;
private static final int FORBIDDEN = 403;
private static final int NOT_FOUND = 404;
private static final int METHOD_NOT_ALLOWED = 405;
private static final int REQUEST_TIMEOUT = 408;
private static final int CONFLICT = 409;
private static final int PRECONDITION_FAILED = 412;
private static final int INTERNAL_SERVER_ERROR = 500;
private static final int BAD_GATEWAY = 502;
private static final int SERVICE_UNAVAILABLE = 503;
private static final int GATEWAY_TIMEOUT = 504;
```
-
第二步,接口请求成功,业务层失败,服务端定义异常状态码
- 比如,登录过期,提醒用户重新登录;
- 比如,添加商品,但是服务端发现库存不足,这个时候接口请求成功,服务端定义业务层失败,服务端给出提示语,客户端进行吐司
- 比如,请求接口,参数异常或者类型错误,请求code为200成功状态,不过给出提示,这个时候客户端用log打印服务端给出的提示语,方便快递查找问题
- 比如,其他情况,接口请求成功,但是服务端定义业务层需要吐司服务端返回的对应提示语
/** * 服务器定义的状态吗 * 比如:登录过期,提醒用户重新登录; * 添加商品,但是服务端发现库存不足,这个时候接口请求成功,服务端定义业务层失败,服务端给出提示语,客户端进行吐司 * 请求接口,参数异常或者类型错误,请求code为200成功状态,不过给出提示,这个时候客户端用log打印服务端给出的提示语,方便快递查找问题 * 其他情况,接口请求成功,但是服务端定义业务层需要吐司服务端返回的对应提示语 */ /** * 完全成功 */ private static final int CODE_SUCCESS = 0; /** * Token 失效 */ public static final int CODE_TOKEN_INVALID = 401; /** * 缺少参数 */ public static final int CODE_NO_MISSING_PARAMETER = 400400; /** * 其他情况 */ public static final int CODE_NO_OTHER = 403; /** * 统一提示 */ public static final int CODE_SHOW_TOAST = 400000;
-
第三步,自定义Http层的异常和服务器定义的异常类
public class HttpException extends Exception { private int code; private String displayMessage; public HttpException(Throwable throwable, int code) { super(throwable); this.code = code; } public void setDisplayMessage(String displayMessage) { this.displayMessage = displayMessage; } public String getDisplayMessage() { return displayMessage; } public int getCode() { return code; } } public class ServerException extends RuntimeException { public int code; public String message; public int getCode() { return code; } public void setCode(int code) { this.code = code; } @Override public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
-
第四步,统一处理异常逻辑如下所示
/** * 这个可以处理服务器请求成功,但是业务逻辑失败,比如token失效需要重新登陆
*/
public static void serviceException(int code , String content){
if (code != CODE_SUCCESS){
ServerException serverException = new ServerException();
serverException.setCode(code);
serverException.setMessage(content);
handleException(serverException);
}
}
/**
* 这个是处理网络异常,也可以处理业务中的异常
* @param e e异常
*/
public static void handleException(Throwable e){
HttpException ex;
//HTTP错误 网络请求异常 比如常见404 500之类的等
if (e instanceof retrofit2.HttpException){
retrofit2.HttpException httpException = (retrofit2.HttpException) e;
ex = new HttpException(e, ErrorCode.HTTP_ERROR);
switch(httpException.code()){
case BAD_REQUEST:
case UNAUTHORIZED:
case FORBIDDEN:
case NOT_FOUND:
case METHOD_NOT_ALLOWED:
case REQUEST_TIMEOUT:
case CONFLICT:
case PRECONDITION_FAILED:
case GATEWAY_TIMEOUT:
case INTERNAL_SERVER_ERROR:
case BAD_GATEWAY:
case SERVICE_UNAVAILABLE:
//均视为网络错误
default:
ex.setDisplayMessage("网络错误"+httpException.code());
break;
}
} else if (e instanceof ServerException){
//服务器返回的错误
ServerException resultException = (ServerException) e;
int code = resultException.getCode();
String message = resultException.getMessage();
ex = new HttpException(resultException, ErrorCode.SERVER_ERROR);
switch (code){
case CODE_TOKEN_INVALID:
ex.setDisplayMessage("token失效");
//下面这里可以统一处理跳转登录页面的操作逻辑
break;
case CODE_NO_OTHER:
ex.setDisplayMessage("其他情况");
break;
case CODE_SHOW_TOAST:
ex.setDisplayMessage("吐司");
break;
case CODE_NO_MISSING_PARAMETER:
ex.setDisplayMessage("缺少参数");
break;
default:
ex.setDisplayMessage(message);
break;
}
} else if (e instanceof JsonParseException
|| e instanceof JSONException
|| e instanceof ParseException){
ex = new HttpException(e, ErrorCode.PARSE_ERROR);
//均视为解析错误
ex.setDisplayMessage("解析错误");
}else if(e instanceof ConnectException){
ex = new HttpException(e, ErrorCode.NETWORK_ERROR);
//均视为网络错误
ex.setDisplayMessage("连接失败");
} else if(e instanceof java.net.UnknownHostException){
ex = new HttpException(e, ErrorCode.NETWORK_ERROR);
//网络未连接
ex.setDisplayMessage("网络未连接");
} else if (e instanceof SocketTimeoutException) {
ex = new HttpException(e, ErrorCode.NETWORK_ERROR);
//网络未连接
ex.setDisplayMessage("服务器响应超时");
} else {
ex = new HttpException(e, ErrorCode.UNKNOWN);
//未知错误
ex.setDisplayMessage("未知错误");
}
String displayMessage = ex.getDisplayMessage();
//这里直接吐司日志异常内容,注意正式项目中一定要注意吐司合适的内容
ToastUtils.showRoundRectToast(displayMessage);
}
```
-
第五步,如何调用
@Override public void onError(Throwable e) { //直接调用即可 ExceptionUtils.handleException(e); }
06.完成版代码展示
-
如下所示
public class ExceptionUtils { /* * 在使用Retrofit+RxJava时,我们访问接口,获取数据的流程一般是这样的:订阅->访问接口->解析数据->展示。
*
* 在获取数据的流程中,访问接口和解析数据时都有可能会出错,我们可以通过拦截器在这两层拦截错误。
* 1.在访问接口时,我们不用设置拦截器,因为一旦出现错误,Retrofit会自动抛出异常。
* 2.在解析数据时,我们设置一个拦截器,判断Result里面的code是否为成功,如果不成功,则要根据与服务器约定好的错误码来抛出对应的异常。
* 3.除此以外,为了我们要尽量避免在View层对错误进行判断,处理,我们必须还要设置一个拦截器,拦截onError事件,然后使用ExceptionHandler,让其根据错误类型来分别处理。
*/
/**
* 对应HTTP的状态码
*/
private static final int BAD_REQUEST = 400;
private static final int UNAUTHORIZED = 401;
private static final int FORBIDDEN = 403;
private static final int NOT_FOUND = 404;
private static final int METHOD_NOT_ALLOWED = 405;
private static final int REQUEST_TIMEOUT = 408;
private static final int CONFLICT = 409;
private static final int PRECONDITION_FAILED = 412;
private static final int INTERNAL_SERVER_ERROR = 500;
private static final int BAD_GATEWAY = 502;
private static final int SERVICE_UNAVAILABLE = 503;
private static final int GATEWAY_TIMEOUT = 504;
/**
* 服务器定义的状态吗
* 比如:登录过期,提醒用户重新登录;
* 添加商品,但是服务端发现库存不足,这个时候接口请求成功,服务端定义业务层失败,服务端给出提示语,客户端进行吐司
* 请求接口,参数异常或者类型错误,请求code为200成功状态,不过给出提示,这个时候客户端用log打印服务端给出的提示语,方便快递查找问题
* 其他情况,接口请求成功,但是服务端定义业务层需要吐司服务端返回的对应提示语
*/
/**
* 完全成功
*/
private static final int CODE_SUCCESS = 0;
/**
* Token 失效
*/
public static final int CODE_TOKEN_INVALID = 401;
/**
* 缺少参数
*/
public static final int CODE_NO_MISSING_PARAMETER = 400400;
/**
* 其他情况
*/
public static final int CODE_NO_OTHER = 403;
/**
* 统一提示
*/
public static final int CODE_SHOW_TOAST = 400000;
/**
* 这个可以处理服务器请求成功,但是业务逻辑失败,比如token失效需要重新登陆
* @param code 自定义的code码
*/
public static void serviceException(int code , String content){
if (code != CODE_SUCCESS){
ServerException serverException = new ServerException();
serverException.setCode(code);
serverException.setMessage(content);
handleException(serverException);
}
}
/**
* 这个是处理网络异常,也可以处理业务中的异常
* @param e e异常
*/
public static void handleException(Throwable e){
HttpException ex;
//HTTP错误 网络请求异常 比如常见404 500之类的等
if (e instanceof retrofit2.HttpException){
retrofit2.HttpException httpException = (retrofit2.HttpException) e;
ex = new HttpException(e, ErrorCode.HTTP_ERROR);
switch(httpException.code()){
case BAD_REQUEST:
case UNAUTHORIZED:
case FORBIDDEN:
case NOT_FOUND:
case METHOD_NOT_ALLOWED:
case REQUEST_TIMEOUT:
case CONFLICT:
case PRECONDITION_FAILED:
case GATEWAY_TIMEOUT:
case INTERNAL_SERVER_ERROR:
case BAD_GATEWAY:
case SERVICE_UNAVAILABLE:
//均视为网络错误
default:
ex.setDisplayMessage("网络错误"+httpException.code());
break;
}
} else if (e instanceof ServerException){
//服务器返回的错误
ServerException resultException = (ServerException) e;
int code = resultException.getCode();
String message = resultException.getMessage();
ex = new HttpException(resultException, ErrorCode.SERVER_ERROR);
switch (code){
case CODE_TOKEN_INVALID:
ex.setDisplayMessage("重新登陆");
break;
case CODE_NO_OTHER:
ex.setDisplayMessage("其他情况");
break;
case CODE_SHOW_TOAST:
ex.setDisplayMessage("吐司");
break;
case CODE_NO_MISSING_PARAMETER:
ex.setDisplayMessage("缺少参数");
break;
default:
ex.setDisplayMessage(message);
break;
}
} else if (e instanceof JsonParseException
|| e instanceof JSONException
|| e instanceof ParseException){
ex = new HttpException(e, ErrorCode.PARSE_ERROR);
//均视为解析错误
ex.setDisplayMessage("解析错误");
}else if(e instanceof ConnectException){
ex = new HttpException(e, ErrorCode.NETWORK_ERROR);
//均视为网络错误
ex.setDisplayMessage("连接失败");
} else if(e instanceof java.net.UnknownHostException){
ex = new HttpException(e, ErrorCode.NETWORK_ERROR);
//网络未连接
ex.setDisplayMessage("网络未连接");
} else if (e instanceof SocketTimeoutException) {
ex = new HttpException(e, ErrorCode.NETWORK_ERROR);
//网络未连接
ex.setDisplayMessage("服务器响应超时");
} else {
ex = new HttpException(e, ErrorCode.UNKNOWN);
//未知错误
ex.setDisplayMessage("未知错误");
}
String displayMessage = ex.getDisplayMessage();
//这里直接吐司日志异常内容,注意正式项目中一定要注意吐司合适的内容
ToastUtils.showRoundRectToast(displayMessage);
}
}
```
其他介绍
01.关于博客汇总链接
02.关于我的博客
- github:https://github.com/yangchong211
- 知乎:https://www.zhihu.com/people/...
- 简书:http://www.jianshu.com/u/b7b2...
- csdn:http://my.csdn.net/m0_37700275
- 喜马拉雅听书:http://www.ximalaya.com/zhubo...
- 开源中国:https://my.oschina.net/zbj161...
- 泡在网上的日子:http://www.jcodecraeer.com/me...
- 邮箱:yangchong211@163.com
- 阿里云博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV
- segmentfault头条:https://segmentfault.com/u/xi...
- 掘金:https://juejin.im/user/593943...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。