资讯专栏INFORMATION COLUMN

网络请求异常拦截优化

lemon / 2917人阅读

目录介绍

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进行类型判断处理

</>复制代码

  1. //请求,对throwable进行判断
  2. ServiceHelper.getInstance()
  3. .getModelResult(param1, param2)
  4. .subscribeOn(Schedulers.io())
  5. .observeOn(AndroidSchedulers.mainThread())
  6. .subscribe(new Subscriber() {
  7. @Override
  8. public void onCompleted() {
  9. }
  10. @Override
  11. public void onError(Throwable e) {
  12. if(e instanceof HttpException){
  13. //获取对应statusCode和Message
  14. HttpException exception = (HttpException)e;
  15. String message = exception.response().message();
  16. int code = exception.response().code();
  17. }else if(e instanceof SSLHandshakeException){
  18. //接下来就是各种异常类型判断...
  19. }else if(e instanceof ...){
  20. }...
  21. }
  22. @Override
  23. public void onNext(Model model) {
  24. if(model.getCode != CODE_SUCCESS){
  25. int code = model.getCode();
  26. switch (code){
  27. case CODE_TOKEN_INVALID:
  28. ex.setDisplayMessage("重新登陆");
  29. break;
  30. case CODE_NO_OTHER:
  31. ex.setDisplayMessage("其他情况");
  32. break;
  33. case CODE_SHOW_TOAST:
  34. ex.setDisplayMessage("吐司服务器返回的提示");
  35. break;
  36. case CODE_NO_MISSING_PARAMETER:
  37. ex.setDisplayMessage("缺少参数,用log记录服务器提示");
  38. break;
  39. default:
  40. ex.setDisplayMessage(message);
  41. break;
  42. }
  43. }else{
  44. //正常处理逻辑
  45. }
  46. }
  47. });

04.如何减少代码耦合性

为了不改变以前的代码结构,那么如何做才能够彻底解耦呢?一般情况下使用Retrofit网络请求框架,会有回调方法,如下所示:

</>复制代码

  1. package retrofit2;
  2. public interface Callback {
  3. void onResponse(Call var1, Response var2);
  4. void onFailure(Call var1, Throwable var2);
  5. }

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

</>复制代码

  1. public class ResponseData {
  2. private int code;
  3. private String message;
  4. private T t;
  5. public int getCode() {
  6. return code;
  7. }
  8. public String getMessage() {
  9. return message;
  10. }
  11. public T getT() {
  12. return t;
  13. }
  14. }
  15. new Callback>(){
  16. @Override
  17. public void onResponse(Call> call,
  18. Response> response) {
  19. int code = response.body().getCode();
  20. String message = response.body().getMessage();
  21. HomeBlogEntity t = response.body().getT();
  22. if (code!= CODE_SUCCESS){
  23. //网络请求成功200,不过业务层执行服务端制定的异常逻辑
  24. ExceptionUtils.serviceException(code,message);
  25. } else {
  26. //网络请求成功,业务逻辑正常处理
  27. }
  28. }
  29. @Override
  30. public void onFailure(Call call, Throwable throwable) {
  31. ExceptionUtils.handleException(throwable);
  32. }
  33. };

05.异常统一处理步骤

第一步:定义请求接口网络层失败的状态码

</>复制代码

  1. /**

</>复制代码

  1. */
  2. private static final int BAD_REQUEST = 400;
  3. private static final int UNAUTHORIZED = 401;
  4. private static final int FORBIDDEN = 403;
  5. private static final int NOT_FOUND = 404;
  6. private static final int METHOD_NOT_ALLOWED = 405;
  7. private static final int REQUEST_TIMEOUT = 408;
  8. private static final int CONFLICT = 409;
  9. private static final int PRECONDITION_FAILED = 412;
  10. private static final int INTERNAL_SERVER_ERROR = 500;
  11. private static final int BAD_GATEWAY = 502;
  12. private static final int SERVICE_UNAVAILABLE = 503;
  13. private static final int GATEWAY_TIMEOUT = 504;
  14. ```

第二步,接口请求成功,业务层失败,服务端定义异常状态码

比如,登录过期,提醒用户重新登录;

比如,添加商品,但是服务端发现库存不足,这个时候接口请求成功,服务端定义业务层失败,服务端给出提示语,客户端进行吐司

比如,请求接口,参数异常或者类型错误,请求code为200成功状态,不过给出提示,这个时候客户端用log打印服务端给出的提示语,方便快递查找问题

比如,其他情况,接口请求成功,但是服务端定义业务层需要吐司服务端返回的对应提示语

</>复制代码

  1. /**
  2. * 服务器定义的状态吗
  3. * 比如:登录过期,提醒用户重新登录;
  4. * 添加商品,但是服务端发现库存不足,这个时候接口请求成功,服务端定义业务层失败,服务端给出提示语,客户端进行吐司
  5. * 请求接口,参数异常或者类型错误,请求code为200成功状态,不过给出提示,这个时候客户端用log打印服务端给出的提示语,方便快递查找问题
  6. * 其他情况,接口请求成功,但是服务端定义业务层需要吐司服务端返回的对应提示语
  7. */
  8. /**
  9. * 完全成功
  10. */
  11. private static final int CODE_SUCCESS = 0;
  12. /**
  13. * Token 失效
  14. */
  15. public static final int CODE_TOKEN_INVALID = 401;
  16. /**
  17. * 缺少参数
  18. */
  19. public static final int CODE_NO_MISSING_PARAMETER = 400400;
  20. /**
  21. * 其他情况
  22. */
  23. public static final int CODE_NO_OTHER = 403;
  24. /**
  25. * 统一提示
  26. */
  27. public static final int CODE_SHOW_TOAST = 400000;

第三步,自定义Http层的异常和服务器定义的异常类

</>复制代码

  1. public class HttpException extends Exception {
  2. private int code;
  3. private String displayMessage;
  4. public HttpException(Throwable throwable, int code) {
  5. super(throwable);
  6. this.code = code;
  7. }
  8. public void setDisplayMessage(String displayMessage) {
  9. this.displayMessage = displayMessage;
  10. }
  11. public String getDisplayMessage() {
  12. return displayMessage;
  13. }
  14. public int getCode() {
  15. return code;
  16. }
  17. }
  18. public class ServerException extends RuntimeException {
  19. public int code;
  20. public String message;
  21. public int getCode() {
  22. return code;
  23. }
  24. public void setCode(int code) {
  25. this.code = code;
  26. }
  27. @Override
  28. public String getMessage() {
  29. return message;
  30. }
  31. public void setMessage(String message) {
  32. this.message = message;
  33. }
  34. }

第四步,统一处理异常逻辑如下所示

</>复制代码

  1. /**
  2. * 这个可以处理服务器请求成功,但是业务逻辑失败,比如token失效需要重新登陆

</>复制代码

  1. */
  2. public static void serviceException(int code , String content){
  3. if (code != CODE_SUCCESS){
  4. ServerException serverException = new ServerException();
  5. serverException.setCode(code);
  6. serverException.setMessage(content);
  7. handleException(serverException);
  8. }
  9. }
  10. /**
  11. * 这个是处理网络异常,也可以处理业务中的异常
  12. * @param e e异常
  13. */
  14. public static void handleException(Throwable e){
  15. HttpException ex;
  16. //HTTP错误 网络请求异常 比如常见404 500之类的等
  17. if (e instanceof retrofit2.HttpException){
  18. retrofit2.HttpException httpException = (retrofit2.HttpException) e;
  19. ex = new HttpException(e, ErrorCode.HTTP_ERROR);
  20. switch(httpException.code()){
  21. case BAD_REQUEST:
  22. case UNAUTHORIZED:
  23. case FORBIDDEN:
  24. case NOT_FOUND:
  25. case METHOD_NOT_ALLOWED:
  26. case REQUEST_TIMEOUT:
  27. case CONFLICT:
  28. case PRECONDITION_FAILED:
  29. case GATEWAY_TIMEOUT:
  30. case INTERNAL_SERVER_ERROR:
  31. case BAD_GATEWAY:
  32. case SERVICE_UNAVAILABLE:
  33. //均视为网络错误
  34. default:
  35. ex.setDisplayMessage("网络错误"+httpException.code());
  36. break;
  37. }
  38. } else if (e instanceof ServerException){
  39. //服务器返回的错误
  40. ServerException resultException = (ServerException) e;
  41. int code = resultException.getCode();
  42. String message = resultException.getMessage();
  43. ex = new HttpException(resultException, ErrorCode.SERVER_ERROR);
  44. switch (code){
  45. case CODE_TOKEN_INVALID:
  46. ex.setDisplayMessage("token失效");
  47. //下面这里可以统一处理跳转登录页面的操作逻辑
  48. break;
  49. case CODE_NO_OTHER:
  50. ex.setDisplayMessage("其他情况");
  51. break;
  52. case CODE_SHOW_TOAST:
  53. ex.setDisplayMessage("吐司");
  54. break;
  55. case CODE_NO_MISSING_PARAMETER:
  56. ex.setDisplayMessage("缺少参数");
  57. break;
  58. default:
  59. ex.setDisplayMessage(message);
  60. break;
  61. }
  62. } else if (e instanceof JsonParseException
  63. || e instanceof JSONException
  64. || e instanceof ParseException){
  65. ex = new HttpException(e, ErrorCode.PARSE_ERROR);
  66. //均视为解析错误
  67. ex.setDisplayMessage("解析错误");
  68. }else if(e instanceof ConnectException){
  69. ex = new HttpException(e, ErrorCode.NETWORK_ERROR);
  70. //均视为网络错误
  71. ex.setDisplayMessage("连接失败");
  72. } else if(e instanceof java.net.UnknownHostException){
  73. ex = new HttpException(e, ErrorCode.NETWORK_ERROR);
  74. //网络未连接
  75. ex.setDisplayMessage("网络未连接");
  76. } else if (e instanceof SocketTimeoutException) {
  77. ex = new HttpException(e, ErrorCode.NETWORK_ERROR);
  78. //网络未连接
  79. ex.setDisplayMessage("服务器响应超时");
  80. } else {
  81. ex = new HttpException(e, ErrorCode.UNKNOWN);
  82. //未知错误
  83. ex.setDisplayMessage("未知错误");
  84. }
  85. String displayMessage = ex.getDisplayMessage();
  86. //这里直接吐司日志异常内容,注意正式项目中一定要注意吐司合适的内容
  87. ToastUtils.showRoundRectToast(displayMessage);
  88. }
  89. ```

第五步,如何调用

</>复制代码

  1. @Override
  2. public void onError(Throwable e) {
  3. //直接调用即可
  4. ExceptionUtils.handleException(e);
  5. }

06.完成版代码展示

如下所示

</>复制代码

  1. public class ExceptionUtils {
  2. /*
  3. * 在使用Retrofit+RxJava时,我们访问接口,获取数据的流程一般是这样的:订阅->访问接口->解析数据->展示。

</>复制代码

  1. *
  2. * 在获取数据的流程中,访问接口和解析数据时都有可能会出错,我们可以通过拦截器在这两层拦截错误。
  3. * 1.在访问接口时,我们不用设置拦截器,因为一旦出现错误,Retrofit会自动抛出异常。
  4. * 2.在解析数据时,我们设置一个拦截器,判断Result里面的code是否为成功,如果不成功,则要根据与服务器约定好的错误码来抛出对应的异常。
  5. * 3.除此以外,为了我们要尽量避免在View层对错误进行判断,处理,我们必须还要设置一个拦截器,拦截onError事件,然后使用ExceptionHandler,让其根据错误类型来分别处理。
  6. */
  7. /**
  8. * 对应HTTP的状态码
  9. */
  10. private static final int BAD_REQUEST = 400;
  11. private static final int UNAUTHORIZED = 401;
  12. private static final int FORBIDDEN = 403;
  13. private static final int NOT_FOUND = 404;
  14. private static final int METHOD_NOT_ALLOWED = 405;
  15. private static final int REQUEST_TIMEOUT = 408;
  16. private static final int CONFLICT = 409;
  17. private static final int PRECONDITION_FAILED = 412;
  18. private static final int INTERNAL_SERVER_ERROR = 500;
  19. private static final int BAD_GATEWAY = 502;
  20. private static final int SERVICE_UNAVAILABLE = 503;
  21. private static final int GATEWAY_TIMEOUT = 504;
  22. /**
  23. * 服务器定义的状态吗
  24. * 比如:登录过期,提醒用户重新登录;
  25. * 添加商品,但是服务端发现库存不足,这个时候接口请求成功,服务端定义业务层失败,服务端给出提示语,客户端进行吐司
  26. * 请求接口,参数异常或者类型错误,请求code为200成功状态,不过给出提示,这个时候客户端用log打印服务端给出的提示语,方便快递查找问题
  27. * 其他情况,接口请求成功,但是服务端定义业务层需要吐司服务端返回的对应提示语
  28. */
  29. /**
  30. * 完全成功
  31. */
  32. private static final int CODE_SUCCESS = 0;
  33. /**
  34. * Token 失效
  35. */
  36. public static final int CODE_TOKEN_INVALID = 401;
  37. /**
  38. * 缺少参数
  39. */
  40. public static final int CODE_NO_MISSING_PARAMETER = 400400;
  41. /**
  42. * 其他情况
  43. */
  44. public static final int CODE_NO_OTHER = 403;
  45. /**
  46. * 统一提示
  47. */
  48. public static final int CODE_SHOW_TOAST = 400000;
  49. /**
  50. * 这个可以处理服务器请求成功,但是业务逻辑失败,比如token失效需要重新登陆
  51. * @param code 自定义的code码
  52. */
  53. public static void serviceException(int code , String content){
  54. if (code != CODE_SUCCESS){
  55. ServerException serverException = new ServerException();
  56. serverException.setCode(code);
  57. serverException.setMessage(content);
  58. handleException(serverException);
  59. }
  60. }
  61. /**
  62. * 这个是处理网络异常,也可以处理业务中的异常
  63. * @param e e异常
  64. */
  65. public static void handleException(Throwable e){
  66. HttpException ex;
  67. //HTTP错误 网络请求异常 比如常见404 500之类的等
  68. if (e instanceof retrofit2.HttpException){
  69. retrofit2.HttpException httpException = (retrofit2.HttpException) e;
  70. ex = new HttpException(e, ErrorCode.HTTP_ERROR);
  71. switch(httpException.code()){
  72. case BAD_REQUEST:
  73. case UNAUTHORIZED:
  74. case FORBIDDEN:
  75. case NOT_FOUND:
  76. case METHOD_NOT_ALLOWED:
  77. case REQUEST_TIMEOUT:
  78. case CONFLICT:
  79. case PRECONDITION_FAILED:
  80. case GATEWAY_TIMEOUT:
  81. case INTERNAL_SERVER_ERROR:
  82. case BAD_GATEWAY:
  83. case SERVICE_UNAVAILABLE:
  84. //均视为网络错误
  85. default:
  86. ex.setDisplayMessage("网络错误"+httpException.code());
  87. break;
  88. }
  89. } else if (e instanceof ServerException){
  90. //服务器返回的错误
  91. ServerException resultException = (ServerException) e;
  92. int code = resultException.getCode();
  93. String message = resultException.getMessage();
  94. ex = new HttpException(resultException, ErrorCode.SERVER_ERROR);
  95. switch (code){
  96. case CODE_TOKEN_INVALID:
  97. ex.setDisplayMessage("重新登陆");
  98. break;
  99. case CODE_NO_OTHER:
  100. ex.setDisplayMessage("其他情况");
  101. break;
  102. case CODE_SHOW_TOAST:
  103. ex.setDisplayMessage("吐司");
  104. break;
  105. case CODE_NO_MISSING_PARAMETER:
  106. ex.setDisplayMessage("缺少参数");
  107. break;
  108. default:
  109. ex.setDisplayMessage(message);
  110. break;
  111. }
  112. } else if (e instanceof JsonParseException
  113. || e instanceof JSONException
  114. || e instanceof ParseException){
  115. ex = new HttpException(e, ErrorCode.PARSE_ERROR);
  116. //均视为解析错误
  117. ex.setDisplayMessage("解析错误");
  118. }else if(e instanceof ConnectException){
  119. ex = new HttpException(e, ErrorCode.NETWORK_ERROR);
  120. //均视为网络错误
  121. ex.setDisplayMessage("连接失败");
  122. } else if(e instanceof java.net.UnknownHostException){
  123. ex = new HttpException(e, ErrorCode.NETWORK_ERROR);
  124. //网络未连接
  125. ex.setDisplayMessage("网络未连接");
  126. } else if (e instanceof SocketTimeoutException) {
  127. ex = new HttpException(e, ErrorCode.NETWORK_ERROR);
  128. //网络未连接
  129. ex.setDisplayMessage("服务器响应超时");
  130. } else {
  131. ex = new HttpException(e, ErrorCode.UNKNOWN);
  132. //未知错误
  133. ex.setDisplayMessage("未知错误");
  134. }
  135. String displayMessage = ex.getDisplayMessage();
  136. //这里直接吐司日志异常内容,注意正式项目中一定要注意吐司合适的内容
  137. ToastUtils.showRoundRectToast(displayMessage);
  138. }
  139. }
  140. ```
其他介绍 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...

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

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

相关文章

  • 网络请求异常拦截优化

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

    番茄西红柿 评论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元查看
<