目录介绍

  • 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);}```
  • 第五步,如何调用

    @Overridepublic 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.关于博客汇总链接

  • 1.技术博客汇总
  • 2.开源项目汇总
  • 3.生活博客汇总
  • 4.喜马拉雅音频汇总
  • 5.其他汇总

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...

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