- 原標題: Beyond Basic RxJava Error Handling
- 原文地址: proandroiddev.com/beyond-basi…
- 原文做者:Elye
今天看到一篇大神 Elye 關於 RxJava 異常的處理的文章,讓我對 RxJava 異常的處理有了一個清晰的瞭解,用了 RxJava 好久了對裏面的異常處理機制一直很懵懂。java
經過這篇文章你將學習到如下內容,將在譯者思考部分會給出相應的答案android
這篇文章涉及不少重要的知識點,請耐心讀下去,應該能夠從中學到不少技巧。git
大部分了解 RxJava 的人都會喜歡它,由於它可以封裝 onError 回調上的錯誤處理,以下所示:github
Single.just(getSomeData())
.map { item -> handleMap(item) }
.subscribe(
{ result -> handleResult(result) },
{ error -> handleError(error) } // Expect all error capture
)
複製代碼
你可能會覺得全部的 Crashes 都將調用 handleError 來處理,但其實並全都是這樣的算法
我先來看一個簡單的例子,假設 crashes 發生在 getSomeData() 方法內部編程
Single.just(getSomeData() /**🔥Crash🔥**/ )
.map { item -> handleMap(item) }
.subscribe(
{ result -> handleResult(result) },
{ error -> handleError(error) } // Crash NOT caught ⛔️
複製代碼
這個錯誤將不會在 handleError 中捕獲,由於 just() 不是 RxJava 調用鏈的一部分,若是你想捕獲它,你可能須要在最外層添加 try-catch 來處理,以下所示:性能優化
try {
Single.just(getSomeData() /**🔥Crash🔥**/ )
.map { item -> handleMap(item) }
.subscribe(
{ result -> handleResult(result) },
{ error -> handleError(error) } // Crash NOT caught ⛔️
)
} catch (exception: Exception) {
handleError(exception) // Crash caught ✅
}
複製代碼
若是你不使用 just ,而是使用 RxJava 內部的一些東西,例如 fromCallable,錯誤將會被捕獲bash
Single.fromCallable{ getSomeData() /**🔥Crash🔥**/ }
.map { item -> handleMap(item) }
.subscribe(
{ result -> handleResult(result) },
{ error -> handleError(error) } // Crash caught ✅
)
複製代碼
讓咱們來假設一下 Crashes 出如今 subscribe success 中,以下所示app
Single.just(getSomeData() )
.map { item -> handleMap(item) }
.subscribe(
{ result -> handleResult(result) /**🔥Crash🔥**/ },
{ error -> handleError(error) } // Crash NOT caught ⛔️
)
複製代碼
這個錯誤將不會被 handleError 捕獲,奇怪的是,若是咱們將 Single 換成 Observable,異常就會被捕獲,以下所示:dom
Observable.just(getSomeData() )
.map { item -> handleMap(item) }
.subscribe(
{ result -> handleResult(result) /**🔥Crash🔥**/ },
{ error -> handleError(error) }, // Crash caught ✅
{ handleCompletion() }
)
複製代碼
緣由是在 Single 中成功的訂閱被認爲是一個完整的流。所以,錯誤再也不能被捕獲。而在 Observable 中,它認爲 onNext 須要處理,所以 crash 仍然能夠被捕獲,那麼咱們應該如何解決這個問題呢
錯誤的處理方式,像以前同樣,在最外層使用 try-catch 進行異常捕獲
try {
Single.just(getSomeData())
.map { item -> handleMap(item) }
.subscribe(
{ result -> handleResult(result)/**🔥Crash🔥**/ },
{ error -> handleError(error) } // Crash NOT caught ⛔️
)
} catch (exception: Exception) {
handleError(exception) // Crash NOT caught ⛔️
}
複製代碼
可是這樣作其實異常並無被捕獲,crash 依然在傳遞,由於 RxJava 在內部處理了 crash,並無傳遞到外部
一種很奇怪的方式,在 subscribe successful 中,執行 try-catch
Single.just(getSomeData() )
.map { item -> handleMap(item) }
.subscribe(
{ result -> try {
handleResult(result) /**🔥Crash🔥**/
} catch (exception: Exception) {
handleError(exception) // Crash caught ✅
}
},
{ error -> handleError(error) }, // Crash NOT caught ⛔️
)
複製代碼
這種方式雖然捕獲住了這個異常,可是 RxJava 並不知道如何處理
一種比較好的方式
上文提到了使用 Single 在 subscribe successful 中不能捕獲異常,由於被認爲是一個完整的流,處理這個狀況比較好的方式,可使用 doOnSuccess 方法
Single.just(getSomeData() )
.map { item -> handleMap(item) }
.doOnSuccess { result -> handleResult(result) /*🔥Crash🔥*/ }, }
.subscribe(
{ /** REMOVE CODE **/ },
{ error -> handleError(error) } // Crash caught ✅
)
複製代碼
當咱們按照上面方式處理的時候,錯誤將會被 onError 捕獲,若是想讓代碼更好看,可使用 doOnError 方法,以下所示:
Single.just(getSomeData() )
.map { item -> handleMap(item) }
.doOnSuccess { result -> handleResult(result) /*🔥Crash🔥*/ }, }
.doOnError { error -> handleError(error) } // Crash NOT stop ⛔️
.subscribe()
複製代碼
可是這並無徹底解決 crash 問題,雖然已經捕獲了但並無中止,所以 crash 仍然發生。
更準確的解釋,它實際上確實捕獲了 crash,可是 doOnError 不是完整狀態,所以錯誤仍應該在 onError 中處理,不然它會在裏面 crash,因此咱們至少應該提供一個空的 onError
Single.just(getSomeData() )
.map { item -> handleMap(item) }
.doOnSuccess { result -> handleResult(result) /*🔥Crash🔥*/ }, }
.doOnError { error -> handleError(error) } // Crash NOT stop ⛔️
.subscribe({} {}) // But crash stop here ✅
複製代碼
咱們來思考一下若是 Crashes 發生在 subscribe error 中怎麼處理,以下所示:
Single.just(getSomeData() )
.map { item -> handleMap(item) }
.subscribe(
{ result -> handleResult(result) },
{ error -> handleError(error) /**🔥Crash🔥**/ }
)
複製代碼
咱們能夠想到使用上文提到的方法來解決這個問題
Single.just(getSomeData() )
.map { item -> handleMap(item) }
.doOnSuccess { result -> handleResult(result) }, }
.doOnError { error -> handleError(error) /*🔥Crash🔥*/ }
.subscribe({} {}) // Crash stop here ✅
複製代碼
儘管這樣能夠避免 crash ,可是仍然很奇怪,由於沒有在 crash 時作任何事情,咱們能夠按照下面的方式,在 onError 中捕獲異常,這是一種很是有趣的編程方式。
Single.just(getSomeData() )
.map { item -> handleMap(item) }
.doOnSuccess { result -> handleResult(result) }, }
.doOnError { error -> handleError(error) /*🔥Crash🔥*/ }
.subscribe({} { error -> handleError(error) }) // Crash caught ✅
複製代碼
無論怎麼樣這個方案是可行的,在這裏只是展現如何處理,後面還會有很好的方式
例如 Observable 除了 onError 和 onNext(還有相似於 Single 的 onSuccess)以外,還有onComplete 狀態。
若是 crashes 發生在以下所示的 onComplete 中,它將不會被捕獲。
Observable.just(getSomeData() )
.map { item -> handleMap(item) }
.subscribe(
{ result -> handleResult(result) },
{ error -> handleError(error) }, // Crash NOT caught ⛔️
{ handleCompletion()/**🔥Crash🔥**/ }
)
複製代碼
咱們能夠按照以前的方法在 doOnComplete 方法中進行處理,以下所示:
Observable.just(getSomeData() )
.map { item -> handleMap(item) }
.doOnNext{ result -> handleResult(result) }
.doOnError{ error -> handleError(error) } Crash NOT stop ⛔️
.doOnComplete { handleCompletion()/**🔥Crash🔥**/ }
.subscribe({ }, { }, { }) // Crash STOP here ✅
複製代碼
最終 crash 可以在 doOnError 中捕獲,並在咱們提供的最後一個空 onError 函數處中止,可是咱們經過這種解決方法逃避了問題。
讓咱們看另外一種有趣的狀況,咱們模擬一種狀況,咱們訂閱的操做很是慢,沒法輕易終止(若是終止,它將crash)
val disposeMe = Observable.fromCallable { Thread.sleep(1000) }
.doOnError{ error -> handleError(error) } // Crash NOT caught ⛔️
.subscribe({}, {}, {}) // Crash NOT caught or stop ⛔️
Handler().postDelayed({ disposeMe.dispose() }, 100)
複製代碼
咱們在 fromCallable 中等待 1000 才能完成,可是在100毫秒,咱們經過調用 disposeMe.dispose() 終止操做。
這將迫使 Thread.sleep(1000)在結束以前終止,從而使其崩潰,沒法經過 doOnError 或者提供的 onError 函數捕獲崩潰
即便咱們在最外面使用 try-catch,也是沒用的,也沒法像其餘全部 RxJava 內部 crash 同樣起做用。
try {
val disposeMe = Observable.fromCallable { Thread.sleep(1000) }
.doOnError{} // Crash NOT caught ⛔️
.subscribe({}, {}, {}) // Crash NOT caught or stop ⛔️
Handler().postDelayed({ disposeMe.dispose() }, 100)
} catch (exception: Exception) {
handleError(exception) // Crash NOT caught too ⛔️
}
複製代碼
對於 RxJava 若是確實發生了 crash,但 crash 不在您的控制範圍內,而且您但願採用一種全局的方式捕獲它,能夠用下面是解決方案。
RxJavaPlugins.setErrorHandler { e -> handleError(e) }
複製代碼
註冊 ErrorHandler 它將捕獲上述任何狀況下的全部 RxJava 未捕獲的錯誤( just() 除外,由於它不屬於RxJava 調用鏈的一部分)
可是要注意用於調用錯誤處理的線程在 crash 發生的地方掛起,若是你想確保它老是發生在主UI線程上,用 runOnUiThread{ } 包括起來
RxJavaPlugins.setErrorHandler { e ->
runOnUiThread { handleError(e))}
}
複製代碼
所以,對於上面的狀況,因爲在完成以前終止而致使 Crash,下面將對此進行處理。
RxJavaPlugins.setErrorHandler { e -> handle(e) } // Crash caught ✅
val disposeMe = Observable.fromCallable { Thread.sleep(1000) }
.doOnError{ error -> handleError(error) } // Crash NOT caught ⛔️
.subscribe({}, {}, {}) // Crash NOT caught or stop ⛔️
Handler().postDelayed({ disposeMe.dispose() }, 100)
複製代碼
有了這個解決方案,並不意味着註冊 ErrorHandler 就是正確的方式
RxJavaPlugins.setErrorHandler { e -> handle(e) }
複製代碼
經過了解上面發生 Crash 處理方案,您就能夠選擇最有效的解決方案,多個方案配合一塊兒使用,以更健壯地處理程序所發生的的 Crash。
做者大概總了 5 種 RxJava 可能出現的異常的位置
總的來講 RxJava 沒法判斷這些超出生命週期的、不可交付的異常中哪些應該或不該該致使應用程序崩潰,最後做者給出了,RxJava Crash 終極解決方案,註冊 ErrorHandler
RxJavaPlugins.setErrorHandler { e -> handleError(e) }
複製代碼
它將捕獲上述任何狀況下的全部 RxJava 未捕獲的錯誤,just() 除外,接下來咱們來了解一下 RxJavaPlugins.setErrorHandler
這是 RxJava2.x 的一個重要設計,如下幾種類型的錯誤 RxJava 是沒法捕獲的:
RxJava 沒法判斷這些超出生命週期的、不可交付的異常中哪些應該或不該該致使應用程序崩潰。
這些沒法捕獲的錯誤,最後會發送到 RxJavaPlugins.onError 處理程序中。這個處理程序能夠用方法RxJavaPlugins.setErrorHandler() 重寫,RxJava 默認狀況下會將 Throwable 的 stacktrace 打印到控制檯,並調用當前線程的未捕獲異常處理程序。
因此咱們能夠採用一種全局的處理方式,註冊一個 RxJavaPlugins.setErrorHandler() ,添加一個非空的全局錯誤處理,下面的示例演示了上面列出來的幾種沒法交付的異常。
RxJavaPlugins.setErrorHandler(e -> {
if (e instanceof UndeliverableException) {
e = e.getCause();
}
if ((e instanceof IOException) || (e instanceof SocketException)) {
// fine, irrelevant network problem or API that throws on cancellation
return;
}
if (e instanceof InterruptedException) {
// fine, some blocking code was interrupted by a dispose call
return;
}
if ((e instanceof NullPointerException) || (e instanceof IllegalArgumentException)) {
// that's likely a bug in the application Thread.currentThread().getUncaughtExceptionHandler() .handleException(Thread.currentThread(), e); return; } if (e instanceof IllegalStateException) { // that's a bug in RxJava or in a custom operator
Thread.currentThread().getUncaughtExceptionHandler()
.handleException(Thread.currentThread(), e);
return;
}
Log.warning("Undeliverable exception received, not sure what to do", e);
});
複製代碼
我相信到這裏關於 RxJava Crash 處理方案,應該瞭解的很清楚了,選擇最有效的解決方案,多個方案配合一塊兒使用,能夠更健壯地處理程序所發生的的 Crash。
接下來咱們來了解一下 just 和 fromCallable 區別 ,做者在另一篇文章 just-vs-fromcallable 作了詳細的介紹
just 值從外部獲取的,而 fromCallable 值來自於內部生成,爲了更清楚的瞭解,咱們來看一下下面的代碼:
println("From Just")
val justSingle = Single.just(Random.nextInt())
justSingle.subscribe{ it -> println(it) }
justSingle.subscribe{ it -> println(it) }
println("\nFrom Callable")
val callableSingle = Single.fromCallable { Random.nextInt() }
callableSingle.subscribe{ it -> println(it) }
callableSingle.subscribe{ it -> println(it) }
複製代碼
對於 Just 和 fromCallable 分別調用 2 次 subscribe 執行結果以下所示:
From Just
801570614
801570614
From Callable
1251601849
2033593269
複製代碼
你會發現對於 just 不管 subscribe 多少次,生成的隨機值都保持不變,由於該值是從 Observable 外部生成的,而 Observable 只是將其存儲供之後使用。
可是對於 fromCallable 它是從 Observable 內部生成的,每次 subscribe 都會都會生成一個新的隨機數。
爲了更清楚的瞭解,咱們來看一下下面的代碼:
fun main() {
println("From Just")
val justSingle = Single.just(getRandomMessage())
println("start subscribing")
justSingle.subscribe{ it -> println(it) }
println("\nFrom Callable")
val callableSingle = Single.fromCallable { getRandomMessage() }
println("start subscribing")
callableSingle.subscribe{ it -> println(it) }
}
fun getRandomMessage(): Int {
println("-Generating-")
return Random.nextInt()
}
複製代碼
結果以下所示:
From Just
-Generating-
start subscribing
1778320787
From Callable
start subscribing
-Generating-
1729786515
複製代碼
對於 just 在調用 subscribe 以前打印了 -Generating-,而 fromCallable 是在調用 subscribe 以後纔打印 -Generating-。
到這裏文章就結束了,咱們來一塊兒探討一個問題, 在 Java 時代 RxJava 確實幫助咱們解決了不少問題,可是相對而言很差的地方 RxJava 裏面各類操做符,理解起來確實很費勁,隨着 Google 將 Kotlin 做爲 Android 首選語言,那麼 RxKotlin,有能給咱們帶來哪些好處呢?
致力於分享一系列 Android 系統源碼、逆向分析、算法、翻譯相關的文章,目前正在翻譯一系列歐美精選文章,請持續關注,除了翻譯還有對每篇歐美文章思考,若是對你有幫助,請幫我點個贊,感謝!!!期待與你一塊兒成長。