Android Retrofit+RxJava 優雅的處理服務器返回異常、錯誤

標籤:java

開始本博客以前,請先閱讀: 
Retrofit請求數據對錯誤以及網絡異常的處理android

異常&錯誤

實際開發常常有這種狀況,好比登陸請求,接口返回的 
信息包括請求返回的狀態:失敗仍是成功,錯誤碼,User對象等等。若是網絡等緣由引發的登陸失敗能夠歸結爲異常,若是是用戶信息輸入錯誤致使的登陸失敗算是錯誤。json

假如服務器返回的是統一數據格式:服務器

/**
 * 標準數據格式
 * @param <T>
 */
public class Response<T> {
    public int state;
    public String message;
    public T data;
}
  • 網絡異常致使的登陸失敗,在使用Retrofit+RxJava請求時都會直接調用subscribe的onError事件;
  • 密碼錯誤致使的登陸失敗,在使用Retrofit+RxJava請求時都會調用subscribe的onNext事件;

不管是異常仍是錯誤,都要在subscribe裏面處理異常信息,以下代碼:markdown

APIWrapper.getInstance().login("username", "password")
                .subscribe(new Observer<Response<User>>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onNext(Response<User> data) {
                        if(data.state == 1001){
                            //.....
                        }else if(data.state == 1002){

                        }
                    }
                });

如今我但願在發生任何錯誤的狀況下,都會調用onError事件,而且由model來處理錯誤信息。那麼,此時咱們就應該有一個ExceptionEngine來處理事件流中的錯誤信息了。網絡

在工做流中處理異常

在Retrofit+RxJava的框架下,咱們獲取網絡數據的流程一般以下:app

訂閱->請求接口->數據解析->更新UI框架

整個數據請求過程都是發生在Rx中的工做流之中。當有異常產生的時候,咱們要儘可能不在ui層裏面進行判斷,換句話說,咱們沒有必要去告訴ui層具體的錯誤信息,只須要讓他彈出一個信息(Toast或者Dialog)展現咱們給它的信息就行。ide

請求接口和數據解析均可能出錯,因此在這兩層進行錯誤處理。爲了更好的解耦,咱們經過攔截器攔截錯誤,而後根據錯誤類型分發信息。ui

攔截器

數據解析層的攔截器

這個攔截器主要是爲了獲取具體的錯誤信息,分發給上層的UI,給用戶以提示,加強用戶體驗。

public Observable<Weather> getWeather(String cityName){
        return weatherService.getWeather(cityName)
                //攔截服務器返回的錯誤
                .map(new ServerResponseFunc<Weather>())
                //HttpResultFunc()爲攔截onError事件的攔截器,後面會講到,這裏先忽略
                .onErrorResumeNext(new HttpResponseFunc<Weather>());
    }
//攔截固定格式的公共數據類型Response<T>,判斷裏面的狀態碼
private class ServerResponseFunc<T> implements Func1<Response<T>, T> {
        @Override
        public T call(Response<T> reponse) {
            //對返回碼進行判斷,若是不是0,則證實服務器端返回錯誤信息了,便根據跟服務器約定好的錯誤碼去解析異常
            if (reponse.state != 0) {
            //若是服務器端有錯誤信息返回,那麼拋出異常,讓下面的方法去捕獲異常作統一處理
                throw new ServerException(reponse.state,reponse.message);
            }
            //服務器請求數據成功,返回裏面的數據實體
            return reponse.data;
        }
    }

因此整個邏輯是這樣的: 
技術分享

因此在前三步的過程當中,只要發生異常(服務器返回的錯誤也拋出了)都會拋出,這時候就觸發了RxJava的OnError事件。

處理onError事件的攔截器

這個攔截器主要是將異常信息轉化爲用戶」能看懂」的友好提示。

private class HttpResponseFunc<T> implements Func1<Throwable, Observable<T>> {
        @Override
        public Observable<T> call(Throwable throwable) {
            //ExceptionEngine爲處理異常的驅動器
            return Observable.error(ExceptionEngine.handleException(throwable));
        }
    }

兩個攔截器之前使用,代碼以下:

public Observable<Weather> getWeather(String cityName){
        return weatherService.getWeather(cityName)
                //攔截服務器返回的錯誤
                .map(new ServerResponseFunc<Weather>())
                //HttpResponseFunc()爲攔截onError事件的攔截器
                .onErrorResumeNext(new HttpResponseFunc<Weather>());
    }

相關類:

public class RxSubscriber<T> extends ErrorSubscriber<T> {

    @Override
    public void onStart() {
        super.onStart();
        DialogHelper.showProgressDlg(context, "正在加載數據");
    }

    @Override
    public void onCompleted() {
        DialogHelper.stopProgressDlg();
    }

    @Override
    protected void onError(ApiException ex) {
        DialogHelper.stopProgressDlg();
        Toast.makeText(context, ex.message, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onNext(T t) {

    }
}


public abstract class ErrorSubscriber<T> extends Subscriber<T> {
    @Override
    public void onError(Throwable e) {
        if(e instanceof ApiException){
            onError((ApiException)e);
        }else{
            onError(new ApiException(e,123));
        }
    }

    /**
     * 錯誤回調
     */
    protected abstract void onError(ApiException ex);
}

處理異常的驅動器

package com.sanniuben.net;

import android.net.ParseException;

import com.google.gson.JsonParseException;

import org.json.JSONException;

import java.net.ConnectException;

import retrofit2.adapter.rxjava.HttpException;

/**
 * Created by Lzx on 2016/7/11.
 */

public class ExceptionEngine {

    //對應HTTP的狀態碼
    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 ApiException handleException(Throwable e){
        ApiException ex;
        if (e instanceof HttpException){             //HTTP錯誤
            HttpException httpException = (HttpException) e;
            ex = new ApiException(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.message = "網絡錯誤";  //均視爲網絡錯誤
                    break;
            }
            return ex;
        } else if (e instanceof ServerException){    //服務器返回的錯誤
            ServerException resultException = (ServerException) e;
            ex = new ApiException(resultException, resultException.code);
            ex.message = resultException.message;
            return ex;
        } else if (e instanceof JsonParseException
                || e instanceof JSONException
                || e instanceof ParseException){
            ex = new ApiException(e, ERROR.PARSE_ERROR);
            ex.message = "解析錯誤";            //均視爲解析錯誤
            return ex;
        }else if(e instanceof ConnectException){
            ex = new ApiException(e, ERROR.NETWORD_ERROR);
            ex.message = "鏈接失敗";  //均視爲網絡錯誤
            return ex;
        }else {
            ex = new ApiException(e, ERROR.UNKNOWN);
            ex.message = "未知錯誤";          //未知錯誤
            return ex;
        }
    }
}

/**
 * 約定異常
 */

public 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 class ApiException extends Exception {
    public int code;
    public String message;

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

    }
}

public class ServerException extends RuntimeException {
    public int code;
    public String message;
}

DialogHelper.java

public class DialogHelper {
    /**
     * 通用Dialog
     *
     */
    // 由於本類不是activity因此經過繼承接口的方法獲取到點擊的事件
    public interface OnOkClickListener {
        abstract void onOkClick();
    }

    /**
     * Listener
     */
    public interface OnCancelClickListener {
        abstract void onCancelClick();
    }

    private static AlertDialog mDialog;

    public static void showDialog(Context context, String title, String content, final OnOkClickListener listenerYes,
                                  final OnCancelClickListener listenerNo) {
        showDialog(context, context.getString(android.R.string.ok), context.getString(android.R.string.cancel), title, content, listenerYes, listenerNo);
    }

    public static void showDialog(Context context, String ok, String cancel, String title, String content, final OnOkClickListener listenerYes,
                                  final OnCancelClickListener listenerNo) {
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setMessage(content);
        // 設置title
        builder.setTitle(title);
        // 設置肯定按鈕,固定用法聲明一個按鈕用這個setPositiveButton
        builder.setPositiveButton(ok,
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        // 若是肯定被電擊
                        if (listenerYes != null) {
                            listenerYes.onOkClick();
                        }
                        mDialog = null;
                    }
                });
        // 設置取消按鈕,固定用法聲明第二個按鈕要用setNegativeButton
        builder.setNegativeButton(cancel,
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        // 若是取消被點擊
                        if (listenerNo != null) {
                            listenerNo.onCancelClick();
                        }
                        mDialog = null;
                    }
                });

        // 控制這個dialog可不能夠按返回鍵,true爲能夠,false爲不能夠
        builder.setCancelable(false);
        // 顯示dialog
        mDialog = builder.create();
        if (!mDialog.isShowing())
            mDialog.show();
    }

    public static void showDialog(Context context, int ok, int cancel, int title, int content, final OnOkClickListener listenerYes,
                                  final OnCancelClickListener listenerNo) {
        showDialog(context, context.getString(ok), context.getString(cancel), context.getString(title), context.getString(content), listenerYes, listenerNo);
    }

    static ProgressDialog progressDlg = null;

    /**
     * 啓動進度條
     *
     * @param strMessage 進度條顯示的信息
     * @param //         當前的activity
     */
    public static void showProgressDlg(Context ctx, String strMessage) {

        if (null == progressDlg) {
            if (ctx == null) return;
            progressDlg = new ProgressDialog(ctx);
            //設置進度條樣式
            progressDlg.setProgressStyle(ProgressDialog.STYLE_SPINNER);
            //提示的消息
            progressDlg.setMessage(strMessage);
            progressDlg.setIndeterminate(false);
            progressDlg.setCancelable(true);
            progressDlg.show();
        }
    }

    public static void showProgressDlg(Context ctx) {
        showProgressDlg(ctx, "");
    }

    /**
     * 結束進度條
     */
    public static void stopProgressDlg() {
        if (null != progressDlg && progressDlg.isShowing()) {
            progressDlg.dismiss();
            progressDlg = null;
        }
        if (null != dialog && dialog.isShowing()) {
            dialog.dismiss();
            dialog = null;
        }
    }

    private static Dialog dialog;

    public static void showDialogForLoading(Context context, String msg, boolean cancelable) {
        if (null == dialog) {
            if (null == context) return;
            View view = LayoutInflater.from(context).inflate(R.layout.layout_loading_dialog, null);
            TextView loadingText = (TextView)view.findViewById(R.id.loading_tip_text);
            loadingText.setText(msg);

            dialog = new Dialog(context, R.style.loading_dialog_style);
            dialog.setCancelable(cancelable);
            dialog.setCanceledOnTouchOutside(cancelable);
            dialog.setContentView(view, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
            Activity activity = (Activity) context;
            if (activity.isFinishing()) return;
            dialog.show();
        }
    }

}
相關文章
相關標籤/搜索