1. 程式人生 > >Retrofit+RxJava 優雅的處理伺服器返回異常、錯誤

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

異常&錯誤

實際開發經常有這種情況,比如登入請求,介面返回的
資訊包括請求返回的狀態:失敗還是成功,錯誤碼,User物件等等。如果網路等原因引起的登入失敗可以歸結為異常,如果是使用者資訊輸入錯誤導致的登入失敗算是錯誤。

假如伺服器返回的是統一資料格式:

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

無論是異常還是錯誤,都要在subscribe裡面處理異常資訊,如下程式碼:

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來處理事件流中的錯誤資訊了。

在工作流中處理異常

在正常情況下,我們獲取網路資料的流程通常如下:

請求介面->解析資料->更新UI

整個資料請求過程都是發生在Rx中的工作流之中。當有異常產生的時候,我們要儘量不在ui層裡面進行判斷,換句話說,我們沒有必要去告訴ui層具體的錯誤資訊,只需要讓他彈出一個資訊(Toast或者Dialog)展示我們給它的資訊就行。

請求介面和資料解析都可能出錯,所以在這兩層進行錯誤處理。為了更好的解耦,我們通過攔截器攔截錯誤,然後根據錯誤型別分發資訊。

攔截器

資料解析層的攔截器

這個攔截器主要是為了獲取具體的錯誤資訊,分發給上層的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>());
    }

呼叫:

APIWrapper.getInstance().getWeather("北京")
                        .subscribe(new SampleProgressObserver<Weather>(MainActivity.this) {
                            @Override
                            public void onNext(WeatherBean weatherBean) {
                                tv.setText(weatherBean.toString());
                            }
                        });

相關類:

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 Observer<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();
        }
    }

}

可能本部落格也不是最好的解決方案,如果有更好的想法,我願與你互相交流!

看到bobo_wang的文章,不僅感覺有受益匪淺,這裡做下介紹。

首先定義如下Transformer轉換器。

public static <T> Observable.Transformer<Response<T>, T> sTransformer() {

   return responseObservable -> responseObservable.map(tResponse -> {
     if (!tResponse.success) throw new RuntimeException(tResponse.code);
     return tResponse.data;
   }).onErrorResumeNext(new HttpResponseFunc<>());
 }

 public static <T> Observable.Transformer<T, T> switchSchedulers() {
    return observable -> observable.subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread());
  }

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

呼叫:

public void login(View v){

  apiservice.login(name,pwd)
      .compose(Transformers.sTransformer())
      .compose(Transformers.switchSchedulers())
      .subscribe(subscriber);

}

private Subscriber<UserModel> subscriber = new Subscriber<UserModel>() {
    @Override public void onCompleted() {
      // do onCompleted
    }

    @Override public void onError(Throwable e) {
      // do on success != true;
      // do on http error
      // do on other error
    }

    @Override public void onNext(UserModel model) {
      // parse data
    }
  };

介面:

@FormUrlEncoded @POST("interface?login")
Observable<Response<UserModel>> login(@Field("name") String name,@Field("pwd") String pwd);

最後再來點乾貨。

Transformer 和 Func 處理的區別

如上的處理,定義了 一個 sTransformer 和一個 HttpResponseFunc,
從中可以明顯感覺的到sTransformer其實也是可以用Func1來定義的,

public void login(View v){

  apiservice.login(name,pwd)
      .compose(Transformers.switchSchedulers())
      .map(new TransFuc<UserModel>())
      .onErrorReturn(new HttpResponseFunc<>())
      .subscribe(subscriber);

}
public static class TransFuc<T> implements Func1<Response<T>, T> {
    @Override public T call(Response<T> tResponse) {
      if (!tResponse.success) throw new RuntimeException(tResponse.code);
      return tResponse.data;
    }
  }

Transformer作用於整個流,Func1是一個操作符,作用於資料項。

不規範資料的處理

有時候伺服器返回的資料並不是十分規範的,比如
正常返回是這樣的

{
    "success": true, // 是否成功
    "status": "1",   // 狀態碼
    "data": {
        // 內容
    }      
}

錯誤時時這樣的

{
    "success": false, // 是否成功
    "status": "0",   // 狀態碼
    "data": "371"   //錯誤碼  
}

這時候如果我麼用泛型處理

public class Response<T> {
    public boolean success;
    public String status;
    public T data;
}

針對這種資料,我們的泛型該怎麼寫成了問題,錯誤的時候是String,正確的時候是Bean?

如果我們直接寫成JavaBean,那麼我們會得到一個錯誤,LinkedTreeMap cannot be cast to xxx
型別轉換錯誤.這時候只能將泛型寫成String來處理了,使用如下Transformer

public static Observable.Transformer<String, UserModel> trans() {
    return stringObservable -> stringObservable.map(s -> {

      Response parseJson = GsonUtil.parseJson(s, Response.class);

      if (null == parseJson) {
        throw new RuntimeException("null == parseJson");
      }

      if (PatternsUtil.isNum(parseJson.data.toString())) {
        throw new RuntimeException(parseJson.data.toString());
      }

      return GsonUtil.parseJson(s, UserModel.class);
    }).onErrorResumeNext(new HttpResponseFunc<>());
  }

使用就變成了如下這樣

public void login(View v){

  apiservice.login(name,pwd)
      .compose(Transformers.switchSchedulers())
       .compose(Transformers.trans())
      .subscribe(subscriber);

}

封裝的越來越簡介了,用到實際專案吧!