淺談RxJava處理業(yè)務異常的幾種方式
本文介紹了RxJava處理業(yè)務異常的幾種方式,分享給大家。具體如下:
關于異常
Java的異??梢苑譃閮煞N:運行時異常和檢查性異常。
運行時異常:
RuntimeException類及其子類都被稱為運行時異常,這種異常的特點是Java編譯器不去檢查它,也就是說,當程序中可能出現(xiàn)這類異常時,即使沒有用try...catch語句捕獲它,也沒有用throws字句聲明拋出它,還是會編譯通過。
檢查性異常:
除了RuntimeException及其子類以外,其他的Exception類及其子類都屬于檢查性異常。檢查性異常必須被顯式地捕獲或者傳遞。當程序中可能出現(xiàn)檢查性異常時,要么使用try-catch語句進行捕獲,要么用throws子句拋出,否則編譯無法通過。
處理業(yè)務異常
業(yè)務異常:
指的是正常的業(yè)務處理時,由于某些業(yè)務的特殊要求而導致處理不能繼續(xù)所拋出的異常。在業(yè)務層或者業(yè)務的處理方法中拋出異常,在表現(xiàn)層中攔截異常,以友好的方式反饋給使用者,以便其可以依據(jù)提示信息正確的完成任務功能的處理。
1. 重試
不是所有的錯誤都需要立馬反饋給用戶,比如說在弱網(wǎng)絡環(huán)境下調用某個接口出現(xiàn)了超時的現(xiàn)象,也許再請求一次接口就能獲得數(shù)據(jù)。那么重試就相當于多給對方一次機會。
在這里,我們使用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)); }
這個例子是一個網(wǎng)絡請求,compose的內容可以忽略。如果網(wǎng)絡請求失敗的話,會調用retryWhen操作符。RetryWithDelay實現(xiàn)了Function接口,RetryWithDelay是一個重試的機制,包含了重試的次數(shù)和重試時間隔的時間。
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); } } }); } }
如果運氣好重試成功了,那用戶在無感知的情況下可以繼續(xù)使用產品。如果多次重試都失敗了,那么必須在onError時做一些異常的處理,提示用戶可能是網(wǎng)絡的原因了。
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操作符,表示當發(fā)生錯誤的時候,發(fā)射一個默認值然后結束數(shù)據(jù)流。所以 Subscriber 看不到異常信息,看到的是正常的數(shù)據(jù)流結束狀態(tài)。
跟它類似的還有onErrorResumeNext操作符,表示當錯誤發(fā)生的時候,使用另外一個數(shù)據(jù)流繼續(xù)發(fā)射數(shù)據(jù)。在返回的被觀察者中是看不到錯誤信息的。
使用了onErrorReturn之后,onError是不是就不做處理了?onErrorReturn的確是返回了一個默認值,如果onErrorReturn之后還有類似doOnNext的操作,并且doOnNext中出錯的話,onError還是會起作用的。
曾經遇到過一個復雜的業(yè)務場景,需要多個網(wǎng)絡請求合并結果。這時,我使用zip操作符,讓請求并行處理,等所有的請求完了之后再進行合并操作。某些請求失敗的話,我使用了重試機制,某些請求失敗的話我給了默認值。
3. 使用onError處理異常
現(xiàn)在的Android開發(fā)中,網(wǎng)絡框架是Retrofit的天下。在接口定義的返回類型中,我一般喜歡用Maybe、Completable來代替Observable。
我們知道RxJava在使用時,觀察者會調用onNext、onError、onComplete方法,其中onError方法是事件在傳遞或者處理的過程中發(fā)生錯誤后會調用到。
下面的代碼,分別封裝兩個基類的Observer,都重寫了onError方法用于處理各種網(wǎng)絡異常。這兩個基類的Observer是在使用Retrofit時使用的。
封裝一個BaseMaybeObserver
import android.accounts.NetworkErrorException import android.content.Context import com.safframework.log.L import io.reactivex.observers.DisposableMaybeObserver import java.net.ConnectException import java.net.SocketTimeoutException import 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.NetworkErrorException import android.content.Context import com.safframework.log.L import io.reactivex.observers.ResourceCompletableObserver import java.net.ConnectException import java.net.SocketTimeoutException import 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. 內部異常使用責任鏈模式來分發(fā)
這是微信中一位網(wǎng)友提供的方法,他做了一個很有意思的用于異常分發(fā)的一個庫,github地址:https://github.com/vihuela/Retrofitplus
內部異常使用責任鏈分發(fā),分發(fā)邏輯為:
- 自定義異常->網(wǎng)絡異常->服務器異常->內部程序異常->未知異常
- 除了以上自定義異常之外,此庫包含其它異常分發(fā),默認適應場景為:Rx+Json
- 自定義異常使用請調用,ExceptionParseMgr類的addCustomerParser方法添加業(yè)務異常
這個庫對原先的代碼無侵入性。此外,他還提供了另一種思路,結合compose來處理一些特定的業(yè)務異常。
總結
本文僅僅是總結了個人使用RxJava遇到業(yè)務異常的情況,并對此做了一些相應地處理,肯定是不能覆蓋開發(fā)的方方面面,僅作為拋磚引玉,如果有更好、更優(yōu)雅的處理方式,一定請告知。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
- JAVA異常處理捕獲與拋出原理解析
- java拋出異常與finally實例解析
- JAVA拋出異常的三種形式詳解
- Java中關于子類覆蓋父類的拋出異常問題
- Java如何將處理完異常之后的程序能夠從拋出異常的地點向下執(zhí)行?
- Java拋出異常與自定義異常類應用示例
- java 拋出異常處理的方法
- 淺談java中異常拋出后代碼是否會繼續(xù)執(zhí)行
- 啟動tomcat時 錯誤: 代理拋出異常 : java.rmi.server.ExportException: Port already in use: 1099的解決辦法
- Java編程中使用throw關鍵字拋出異常的用法簡介
- Java 如何優(yōu)雅的拋出業(yè)務異常
相關文章
Android實現(xiàn)網(wǎng)易Tab分類排序控件實現(xiàn)
這篇文章主要為大家詳細介紹了Android仿網(wǎng)易Tab分類排序控件的實現(xiàn)代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-03-03Android編程實現(xiàn)長按Button按鈕連續(xù)響應功能示例
這篇文章主要介紹了Android編程實現(xiàn)長按Button按鈕連續(xù)響應功能,涉及Android自定義按鈕及事件響應操作相關技巧,需要的朋友可以參考下2017-01-01詳解如何使用VisualStudio高效開發(fā)調試AndroidNDK
這篇文章主要介紹了詳解如何使用VisualStudio高效開發(fā)調試AndroidNDK,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-12-12