標簽:
開始本博客之前,請先閱讀:
Retrofit請求數據對錯誤以及網絡異常的處理
異常&錯誤
實際開發經常有這種情況,比如登錄請求,接口返回的
信息包括請求返回的狀態:失敗還是成功,錯誤碼,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來處理事件流中的錯誤信息了。
在工作流中處理異常
在Retrofit+RxJava的框架下,我們獲取網絡數據的流程通常如下:
訂閱->請求接口->數據解析->更新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>()); }
相關類:
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(); } } }