网络请求异常拦截优化

目录介绍

  • 01.网络请求异常分类
  • 02.开发中注意问题
  • 03.原始的处理方式
  • 04.如何减小代码耦合性
  • 05.异常统一处理步骤
  • 06.完成版代码展现

好消息

  • 博客笔记大汇总【16年3月到至今】,包括Java基础及深刻知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,固然也在工做之余收集了大量的面试题,长期更新维护而且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢!
  • 连接地址:https://github.com/yangchong211/YCBlogs
  • 若是以为好,能够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.异常统一处理步骤

  • 第一步:定义请求接口网络层失败的状态码
    /** * 对应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打印服务端给出的提示语,方便快递查找问题
    • 好比,其余状况,接口请求成功,可是服务端定义业务层须要吐司服务端返回的对应提示语
    /** * 服务器定义的状态吗 * 好比:登陆过时,提醒用户从新登陆; * 添加商品,可是服务端发现库存不足,这个时候接口请求成功,服务端定义业务层失败,服务端给出提示语,客户端进行吐司 * 请求接口,参数异常或者类型错误,请求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失效须要从新登录 * @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("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时,咱们访问接口,获取数据的流程通常是这样的:订阅->访问接口->解析数据->展现。 * 如上所说,异常和错误本质是同样的,所以咱们要尽可能避免在View层对错误进行判断,处理。 * * 在获取数据的流程中,访问接口和解析数据时都有可能会出错,咱们能够经过拦截器在这两层拦截错误。 * 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.关于个人博客

开源代码案例:https://github.com/yangchong211/LifeHelper