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

