本文介紹了RxJava處理業務異常的幾種方式,分享給大家。具體如下:
關于異常
Java的異常可以分為兩種:運行時異常和檢查性異常。
運行時異常:
RuntimeException類及其子類都被稱為運行時異常,這種異常的特點是Java編譯器不去檢查它,也就是說,當程序中可能出現這類異常時,即使沒有用try...catch語句捕獲它,也沒有用throws字句聲明拋出它,還是會編譯通過。
檢查性異常:
除了RuntimeException及其子類以外,其他的Exception類及其子類都屬于檢查性異常。檢查性異常必須被顯式地捕獲或者傳遞。當程序中可能出現檢查性異常時,要么使用try-catch語句進行捕獲,要么用throws子句拋出,否則編譯無法通過。
處理業務異常
業務異常:
指的是正常的業務處理時,由于某些業務的特殊要求而導致處理不能繼續所拋出的異常。在業務層或者業務的處理方法中拋出異常,在表現層中攔截異常,以友好的方式反饋給使用者,以便其可以依據提示信息正確的完成任務功能的處理。
1. 重試
不是所有的錯誤都需要立馬反饋給用戶,比如說在弱網絡環境下調用某個接口出現了超時的現象,也許再請求一次接口就能獲得數據。那么重試就相當于多給對方一次機會。
在這里,我們使用retryWhen操作符,它將錯誤傳遞給另一個被觀察者來決定是否要重新給訂閱這個被觀察者。
聽上去有點拗口,直接上代碼吧。
 /**  * 獲取內容  * @param fragment  * @param param  * @param cacheKey  * @return  */ public Maybe<ContentModel> getContent(Fragment fragment, ContentParam param, String cacheKey) {  if (apiService == null) {   apiService = RetrofitManager.get().apiService();  }  return apiService.loadContent(param)    .retryWhen(new RetryWithDelay(3,1000))    .compose(RxLifecycle.bind(fragment).<ContentModel>toLifecycleTransformer())    .compose(RxUtils.<ContentModel>toCacheTransformer(cacheKey)); }這個例子是一個網絡請求,compose的內容可以忽略。如果網絡請求失敗的話,會調用retryWhen操作符。RetryWithDelay實現了Function接口,RetryWithDelay是一個重試的機制,包含了重試的次數和重試時間隔的時間。
import com.safframework.log.L;import org.reactivestreams.Publisher;import java.util.concurrent.TimeUnit;import io.reactivex.Flowable;import io.reactivex.annotations.NonNull;import io.reactivex.functions.Function;/** * 重試機制 * Created by tony on 2017/11/6. */public class RetryWithDelay implements Function<Flowable<? extends Throwable>, Publisher<?>> { private final int maxRetries; private final int retryDelayMillis; private int retryCount; public RetryWithDelay(final int maxRetries, final int retryDelayMillis) {  this.maxRetries = maxRetries;  this.retryDelayMillis = retryDelayMillis;  this.retryCount = 0; } @Override public Publisher<?> apply(@NonNull Flowable<? extends Throwable> attempts) throws Exception {  return attempts.flatMap(new Function<Throwable, Publisher<?>>() {   @Override   public Publisher<?> apply(Throwable throwable) throws Exception {    if (++retryCount <= maxRetries) {     L.i("RetryWithDelay", "get error, it will try after " + retryDelayMillis       + " millisecond, retry count " + retryCount);     // When this Observable calls onNext, the original     // Observable will be retried (i.e. re-subscribed).     return Flowable.timer(retryDelayMillis, TimeUnit.MILLISECONDS);    } else {     // Max retries hit. Just pass the error along.     return Flowable.error(throwable);    }   }  }); }}如果運氣好重試成功了,那用戶在無感知的情況下可以繼續使用產品。如果多次重試都失敗了,那么必須在onError時做一些異常的處理,提示用戶可能是網絡的原因了。
2. 返回一個默認值
有時出錯只需返回一個默認值,有點類似Java 8 Optional的orElse()
RetrofitManager.get()    .adService()    .vmw(param)    .compose(RxLifecycle.bind(fragment).<VMWModel>toLifecycleTransformer())    .subscribeOn(Schedulers.io())    .onErrorReturn(new Function<Throwable, VMWModel>() {     @Override     public VMWModel apply(Throwable throwable) throws Exception {      return new VMWModel();     }    });上面的例子使用了onErrorReturn操作符,表示當發生錯誤的時候,發射一個默認值然后結束數據流。所以 Subscriber 看不到異常信息,看到的是正常的數據流結束狀態。
跟它類似的還有onErrorResumeNext操作符,表示當錯誤發生的時候,使用另外一個數據流繼續發射數據。在返回的被觀察者中是看不到錯誤信息的。
使用了onErrorReturn之后,onError是不是就不做處理了?onErrorReturn的確是返回了一個默認值,如果onErrorReturn之后還有類似doOnNext的操作,并且doOnNext中出錯的話,onError還是會起作用的。
曾經遇到過一個復雜的業務場景,需要多個網絡請求合并結果。這時,我使用zip操作符,讓請求并行處理,等所有的請求完了之后再進行合并操作。某些請求失敗的話,我使用了重試機制,某些請求失敗的話我給了默認值。
3. 使用onError處理異常
現在的Android開發中,網絡框架是Retrofit的天下。在接口定義的返回類型中,我一般喜歡用Maybe、Completable來代替Observable。
我們知道RxJava在使用時,觀察者會調用onNext、onError、onComplete方法,其中onError方法是事件在傳遞或者處理的過程中發生錯誤后會調用到。
下面的代碼,分別封裝兩個基類的Observer,都重寫了onError方法用于處理各種網絡異常。這兩個基類的Observer是在使用Retrofit時使用的。
封裝一個BaseMaybeObserver
import android.accounts.NetworkErrorExceptionimport android.content.Contextimport com.safframework.log.Limport io.reactivex.observers.DisposableMaybeObserverimport java.net.ConnectExceptionimport java.net.SocketTimeoutExceptionimport java.net.UnknownHostException/** * Created by Tony Shen on 2017/8/8. */abstract class BaseMaybeObserver<T> : DisposableMaybeObserver<T>() { internal var mAppContext: Context init {  mAppContext = AppUtils.getApplicationContext() } override fun onSuccess(data: T) {  onMaybeSuccess(data) } abstract fun onMaybeSuccess(data: T) override fun onError(e: Throwable) {  var message = e.message  L.e(message)  when(e) {   is ConnectException -> message = mAppContext.getString(R.string.connect_exception_error)   is SocketTimeoutException -> message = mAppContext.getString(R.string.timeout_error)   is UnknownHostException -> message = mAppContext.getString(R.string.network_error)   is NetworkErrorException -> message = mAppContext.getString(R.string.network_error)   else -> message = mAppContext.getString(R.string.something_went_wrong)  }  RxBus.get().post(FailedEvent(message)) } override fun onComplete() {}}封裝一個BaseCompletableObserver
import android.accounts.NetworkErrorExceptionimport android.content.Contextimport com.safframework.log.Limport io.reactivex.observers.ResourceCompletableObserverimport java.net.ConnectExceptionimport java.net.SocketTimeoutExceptionimport java.net.UnknownHostException/** * Created by Tony Shen on 2017/8/8. */abstract class BaseCompletableObserver : ResourceCompletableObserver() { internal var mAppContext: Context init {  mAppContext = AppUtils.getApplicationContext() } override fun onComplete() {  onSuccess() } abstract fun onSuccess() override fun onError(e: Throwable) {  var message = e.message  L.e(message)  when(e) {   is ConnectException -> message = mAppContext.getString(R.string.connect_exception_error)   is SocketTimeoutException -> message = mAppContext.getString(R.string.timeout_error)   is UnknownHostException -> message = mAppContext.getString(R.string.network_error)   is NetworkErrorException -> message = mAppContext.getString(R.string.network_error)   else -> message = mAppContext.getString(R.string.something_went_wrong)  }  RxBus.get().post(FailedEvent(message)) }}在這里用到了Kotlin來寫這兩個基類,使用Kotlin的目的是因為代碼更加簡潔,避免使用switch或者各種if(XX instancof xxException)來判斷異常類型,可以跟Java代碼無縫結合。
下面的代碼展示了如何使用BaseMaybeObserver,即使遇到異常BaseMaybeObserver的onError也會做相應地處理。如果有特殊的需求,也可以重寫onError方法。
    model.getContent(VideoFragment.this,param, cacheKey)      .compose(RxJavaUtils.<ContentModel>maybeToMain())      .doFinally(new Action() {       @Override       public void run() throws Exception {        refreshlayout.finishRefresh();       }      })      .subscribe(new BaseMaybeObserver<ContentModel>(){     @Override     public void onMaybeSuccess(ContentModel data) {      adpter.addDataToFront(data);     }    });4. 內部異常使用責任鏈模式來分發
這是微信中一位網友提供的方法,他做了一個很有意思的用于異常分發的一個庫,github地址:https://github.com/vihuela/Retrofitplus
內部異常使用責任鏈分發,分發邏輯為:
這個庫對原先的代碼無侵入性。此外,他還提供了另一種思路,結合compose來處理一些特定的業務異常。
新聞熱點
疑難解答