1. 程式人生 > >Android RxJava+Retrofit網路異常、狀態碼統一處理

Android RxJava+Retrofit網路異常、狀態碼統一處理

Android RxJava+Retrofit 網路異常捕獲、狀態碼統一處理

前言

近來使用RxJava+Retrofit進行開發,在專案中遇到這樣一個需求,聯網請求獲得資料異常時,需要將對應的Message和StatusCode進行獲得並展示,比如:
1.伺服器連線Error: 對應的返回404,500等等;
2.沒有網路狀態(沒有4g,3g,是否處於wifi環境下等);

一、簡單的獲取處理

最簡單的處理方式,直接對返回的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的原始碼地址:點我點我

2018/9/25更新

時隔一年,筆者發現本文的實現方式,在很多情況下有 很大的限制性(直白點吧,就是上文的方式太low了),因此修改了實現的方式,並歸納出一個在我看來 更靈活,更輕量 的工具元件,更適合 RxJava2的重度使用者,詳情請參考: