资讯专栏INFORMATION COLUMN

网络请求异常拦截优化

番茄西红柿 / 1866人阅读

目录介绍

01.网络请求异常分类

02.开发中注意问题

03.原始的处理方式

04.如何减少代码耦合性

05.异常统一处理步骤

06.完成版代码展示

好消息

博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢!

链接地址:github.com/yangchong21…

如果觉得好,可以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() {
             @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 {
    void onResponse(Call var1, Response var2);

    void onFailure(Call var1, Throwable var2);
}

不管以前代码封装与否,都希望一句代码即可实现网络请求拦截处理逻辑。那么这个时候,我是怎么处理的呢?

public class ResponseData {

    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 CallbacklogEntity>>(){
    @Override
    public void onResponse(CalllogEntity>> call,
                           ResponselogEntity>> 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.关于博客汇总链接

1.技术博客汇总

2.开源项目汇总

3.生活博客汇总

4.喜马拉雅音频汇总

5.其他汇总

02.关于我的博客

github:github.com/yangchong21…

知乎:www.zhihu.com/people/yczb…

简书:www.jianshu.com/u/b7b2c6ed9…

csdn:my.csdn.net/m0_37700275

喜马拉雅听书:www.ximalaya.com/zhubo/71989…

开源中国:my.oschina.net/zbj1618/blo…

泡在网上的日子:www.jcodecraeer.com/member/cont…

邮箱:yangchong211@163.com

阿里云博客:yq.aliyun.com/users/artic… 239.headeruserinfo.3.dT4bcV

segmentfault头条:segmentfault.com/u/xiangjian…

掘金:juejin.im/user/593943…

开源代码案例:github.com/yangchong21…

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/7056.html

相关文章

  • 网络请求异常拦截优化

    目录介绍 01.网络请求异常分类 02.开发中注意问题 03.原始的处理方式 04.如何减少代码耦合性 05.异常统一处理步骤 06.完成版代码展示 好消息 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是ma...

    lemon 评论0 收藏0
  • 网络请求异常拦截优化

    目录介绍 01.网络请求异常分类 02.开发中注意问题 03.原始的处理方式 04.如何减少代码耦合性 05.异常统一处理步骤 06.完成版代码展示 好消息 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是m...

    moven_j 评论0 收藏0
  • Android开发优化的几点建议

    摘要:网络数据优化移动端获取网络数据优化可以从以下几点着手连接复用节省连接建立时间,如开启。 安卓开发大军浩浩荡荡,经过近十年的发展,Android技术优化日异月新,如今Android 9.0 已经发布,Android系统性能也已经非常流畅,可以在体验上完全媲美iOS。但是,到了各大厂商手里,改源码、自定义系统,使得Android原生系统变得鱼龙混杂,然后到了不同层次的开发工程师手里,因为技...

    赵连江 评论0 收藏0
  • Android 网络优化,使用 HTTPDNS 优化 DNS,从原理到 OkHttp 集成

    摘要:使用时,只需要在中,调用方法来注册此拦截器即可。在拦截器中,使用这个帮助类,通过将转为对应的。服务端根据请求,选择合适的算法,下发公钥证书和随机数。客户端对服务端证书,进行校验,并发送随机数信息,该信息使用公钥加密。 showImg(https://segmentfault.com/img/remote/1460000018642192); 一、前言 谈到优化,首先第一步,肯定是把一个...

    livem 评论0 收藏0
  • Android优化总结

    摘要:错误使用单利在开发中单例经常需要持有对象,如果持有的对象生命周期与单例生命周期更短时,或导致无法被释放回收,则有可能造成内存泄漏。如果集合是类型的话,那内存泄漏情况就会更为严重。 目录介绍 1.OOM和崩溃优化 1.1 OOM优化 1.2 ANR优化 1.3 Crash优化 2.内存泄漏优化 2.0 动画资源未释放 2.1 错误使用单利 2.2 错误使用静态变量 2.3 ...

    sunsmell 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<