[译] 解密 RxJava 的异常处理机制

前言

今天看到一篇大神 Elye 关于 RxJava 异常的处理的文章,让我对 RxJava 异常的处理有了一个清晰的了解,用了 RxJava 好久了对里面的异常处理机制一直很懵懂。java

经过这篇文章你将学习到如下内容,将在译者思考部分会给出相应的答案android

  • just 和 fromCallable 区别?
  • 什么是 RxJavaPlugins.setErrorHandler?
  • Crashes 发生在 just() 中的处理方案?
  • Crashes 发生在 subscribe success 中的处理方案?
  • Crashes 发生在 subscribe error 中的处理方案?
  • Crashes 发生在 complete 中的处理方案?
  • Crashes 发生在 complete 以前的处理方案?

这篇文章涉及不少重要的知识点,请耐心读下去,应该能够从中学到不少技巧。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 发生在 just() 中

我先来看一个简单的例子,假设 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 中

让咱们来假设一下 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 中

咱们来思考一下若是 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 ✅
复制代码

无论怎么样这个方案是可行的,在这里只是展现如何处理,后面还会有很好的方式

Crashes 发生在 complete 中

例如 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 函数处中止,可是咱们经过这种解决方法逃避了问题。

Crashes 发生在 complete 以前

让咱们看另外一种有趣的状况,咱们模拟一种状况,咱们订阅的操做很是慢,没法轻易终止(若是终止,它将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 终极解决方案

对于 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 可能出现的异常的位置

  • Crashes 发生在 just() 中
  • Crashes 发生在 subscribe success 中
  • Crashes 发生在 subscribe error 中
  • Crashes 发生在 complete 中
  • Crashes 发生在 complete 以前

总的来讲 RxJava 没法判断这些超出生命周期的、不可交付的异常中哪些应该或不该该致使应用程序崩溃,最后做者给出了,RxJava Crash 终极解决方案,注册 ErrorHandler

RxJavaPlugins.setErrorHandler { e -> handleError(e) }
复制代码

它将捕获上述任何状况下的全部 RxJava 未捕获的错误,just() 除外,接下来咱们来了解一下 RxJavaPlugins.setErrorHandler

关于 RxJavaPlugins.setErrorHandler

这是 RxJava2.x 的一个重要设计,如下几种类型的错误 RxJava 是没法捕获的:

  • 因为下游的生命周期已经达到其终端状态致使的异常
  • 下游取消了将要发出错误的序列而没法发出的错误
  • 发生了 crash ,可是 crash 不在您的控制范围内
  • 一些第三方库的代码在被取消 或者 调用中断时会抛出该异常,这一般会致使没法交付的异常。

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 区别

1. 值获取来源不同

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 都会都会生成一个新的随机数。

2. 当即执行和延迟执行

  • just 在调用 subscribe 方法以前值已经生成了,属于当即执行。
  • 而 fromCallable 是调用 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 系统源码、逆向分析、算法、翻译相关的文章,目前正在翻译一系列欧美精选文章,请持续关注,除了翻译还有对每篇欧美文章思考,若是对你有帮助,请帮我点个赞,感谢!!!期待与你一块儿成长。

文章列表

Android 10 源码系列

Android 应用系列

工具系列

逆向系列

相关文章
相关标签/搜索