Android RxJava+Retrofit网络异常状态码统一处理

Posted 却把清梅嗅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android RxJava+Retrofit网络异常状态码统一处理相关的知识,希望对你有一定的参考价值。

Android RxJava+Retrofit 网络异常捕获、状态码统一处理

前言

近来使用RxJava+Retrofit进行开发,在项目中遇到这样一个需求,联网请求获得数据异常时,需要将对应的Message和StatusCode进行获得并展示,比如:
1.服务器连接Error: 对应的返回404,500等等;
2.没有网络状态(没有4g,3g,是否处于wifi环境下等);

参考文章:
Rxjava +Retrofit 你需要掌握的几个技巧,Retrofit缓存,RxJava封装,统一对有无网络处理,异常处理, 返回结果问题 @码小白

一、简单的获取处理

最简单的处理方式,直接对返回的throwable进行类型判断处理:

//先判断网络环境
if(!NetUtils.is4G(context))
	return;

//再联网请求,对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) 
	
	              
          );

显然,代码并不难以理解,但是非常麻烦,总不至于每次都要进行这样的大块代码复制粘贴吧。
请注意,进行类型判断的的HttpException为

import retrofit2.adapter.rxjava.HttpException;

所以需要我们依赖:

compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'

二、NetErrorUtil封装

1.首先我们创建一个Throwable的管理类

public class ExceptionHandle 

    private static final int UNAUTHORIZED = 401;
    private static final int FORBIDDEN = 403;
    private static final int NOT_FOUND = 404;
    private static final int REQUEST_TIMEOUT = 408;
    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;

    public static ResponeThrowable handleException(Throwable e) 
        ResponeThrowable ex;
        Log.i("tag", "e.toString = " + e.toString());
        if (e instanceof HttpException) 
            HttpException httpException = (HttpException) e;
            ex = new ResponeThrowable(e, ERROR.HTTP_ERROR);
            switch (httpException.code()) 
                case UNAUTHORIZED:
                case FORBIDDEN:
                case NOT_FOUND:
                case REQUEST_TIMEOUT:
                case GATEWAY_TIMEOUT:
                case INTERNAL_SERVER_ERROR:
                case BAD_GATEWAY:
                case SERVICE_UNAVAILABLE:
                default:
                    //ex.code = httpException.code();
                    ex.message = "网络错误";
                    break;
            
            return ex;
         else if (e instanceof ServerException) 
            ServerException resultException = (ServerException) e;
            ex = new ResponeThrowable(resultException, resultException.code);
            ex.message = resultException.message;
            return ex;
         else if (e instanceof JsonParseException
                || e instanceof JSONException
                /*|| e instanceof ParseException*/) 
            ex = new ResponeThrowable(e, ERROR.PARSE_ERROR);
            ex.message = "解析错误";
            return ex;
         else if (e instanceof ConnectException) 
            ex = new ResponeThrowable(e, ERROR.NETWORD_ERROR);
            ex.message = "连接失败";
            return ex;
         else if (e instanceof javax.net.ssl.SSLHandshakeException) 
            ex = new ResponeThrowable(e, ERROR.SSL_ERROR);
            ex.message = "证书验证失败";
            return ex;
         else 
            ex = new ResponeThrowable(e, ERROR.UNKNOWN);
            ex.message = "未知错误";
            return ex;
        
    


    /**
     * 约定异常
     */
    class ERROR 
        /**
         * 未知错误
         */
        public static final int UNKNOWN = 1000;
        /**
         * 解析错误
         */
        public static final int PARSE_ERROR = 1001;
        /**
         * 网络错误
         */
        public static final int NETWORD_ERROR = 1002;
        /**
         * 协议出错
         */
        public static final int HTTP_ERROR = 1003;

        /**
         * 证书出错
         */
        public static final int SSL_ERROR = 1005;
    

    public static class ResponeThrowable extends Exception 
        public int code;
        public String message;

        public ResponeThrowable(Throwable throwable, int code) 
            super(throwable);
            this.code = code;
        
    

    /**
     * ServerException发生后,将自动转换为ResponeThrowable返回
     */
    class ServerException extends RuntimeException 
        int code;
        String message;
    


在这个类中,我们把throwable进行类型统一判断:
1.若为Exception,我们再进行判断,根据不同种类的Exception,转化为ResponeThrowable返回,并赋予不同的code和message;
2.若为RuntimeException,我们再转换为ResponeThrowable返回;

总而言之,我们把获取到的异常,通过handleException()方法,转化为统一的ResponeThrowable返回,这个对象包含code和message。

2.然后是创建一个新的Subscriber基类

/**
 * Subscriber基类,可以在这里处理client网络连接状况
 * (比如没有wifi,没有4g,没有联网等)
 * Created by fcn-mq on 2017/4/19.
 */

public abstract class MySubscriber<T> extends Subscriber<T> 

    private Context context;

    public MySubscriber(Context context) 
        this.context = context;
    

    @Override
    public void onStart() 
        super.onStart();
        Log.i("tag","MySubscriber.onStart()");
        //接下来可以检查网络连接等操作
        if (!NetworkUtil.isNetworkAvailable(context)) 

            Toast.makeText(context, "当前网络不可用,请检查网络情况", Toast.LENGTH_SHORT).show();
            // 一定好主动调用下面这一句,取消本次Subscriber订阅
            if (!isUnsubscribed()) 
                unsubscribe();
            
            return;
        
    

    @Override
    public void onError(Throwable e) 
        Log.e("tag","MySubscriber.throwable ="+e.toString());
        Log.e("tag","MySubscriber.throwable ="+e.getMessage());

        if(e instanceof Exception)
            //访问获得对应的Exception
            onError(ExceptionHandle.handleException(e));
        else 
            //将Throwable 和 未知错误的status code返回
            onError(new ExceptionHandle.ResponeThrowable(e,ExceptionHandle.ERROR.UNKNOWN));
        
    
    
	public abstract void onError(ExceptionHandle.ResponeThrowable responeThrowable);
    
    @Override
    public void onCompleted() 
        Log.i("tag","MySubscriber.onComplete()");
    

这个就好理解了,我们可以在基类中统一进行客户端网络环境的判断,或者当网络异常发生时,统一转换为
ResponeThrowable,在onError(ExceptionHandle.ResponeThrowable responeThrowable)中回调,这个abstract的回调方法需要我们自己实现。

3.代码中使用:

new Retrofit.Builder()
	        .baseUrl(ConstantsApi.BASE_DOUBAN)
	         .addConverterFactory(GsonConverterFactory.create())
	         .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
	         .client(client)
	         .build()
	         .create(DoubanMovieService.class)//获得对应的service对象
	         .getModelResult(param1, param2)  //网络请求
	         .subscribeOn(Schedulers.io())
	         .observeOn(AndroidSchedulers.mainThread())
	         .subscribe(new MySubscriber<Model>(context) 
	             @Override
	             public void onNext(Model model) 
	               ...
	             
	
	             @Override
	             public void onError(ExceptionHandle.ResponeThrowable throwable) 
	                 LogUtil.m("tag","throwable =" + throwable.toString());
	                 //接下来就可以根据状态码进行处理...
	                 int statusCode = throwable.code;
	                 switch (statusCode)
	                     case ExceptionHandle.ERROR.SSL_ERROR:
	                      break;
	                     case ExceptionHandle.ERROR.UNKNOWN:
	                      break;
	                     case ExceptionHandle.ERROR.PARSE_ERROR:
	                      break;
	                     case ExceptionHandle.ERROR.NETWORD_ERROR:
	                         break;
	                     case ExceptionHandle.ERROR.HTTP_ERROR:
	                   break;
	                 
	             
	         );

最后是NetErrorUtil的源码地址:点我点我

【GitHub地址】https://github.com/ButQingMei/Samples.git

2018/9/25更新

时隔一年,笔者发现本文的实现方式,在很多情况下有 很大的限制性(直白点吧,就是上文的方式太low了),因此修改了实现的方式,并归纳出一个在我看来 更灵活,更轻量 的工具组件,更适合 RxJava2的重度使用者,详情请参考:

不要打破链式调用!一个极低成本的RxJava2全局Error处理方案

以上是关于Android RxJava+Retrofit网络异常状态码统一处理的主要内容,如果未能解决你的问题,请参考以下文章

Android OkHttp+Retrofit+Rxjava+Hilt实现网络请求框架

Android OkHttp+Retrofit+Rxjava+Hilt实现网络请求框架

Android OkHttp+Retrofit+Rxjava+Hilt实现网络请求框架

Android OkHttp+Retrofit+Rxjava+Hilt实现网络请求框架

Android 教你一步步搭建MVP+Retrofit+RxJava网络请求框架

Android开发之Retrofit+RxJava的使用