解密 RxJava 的异常处理机制【转】

转自

1前言

本文为译文,译文地址:
https://proandroiddev.com/beyond-basic-rxjava-error-handling-b0875d3877e0

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

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

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

这篇文章涉及很多重要的知识点,请耐心读下去,应该可以从中学到很多技巧。

2译文

大部分了解 RxJava 的人都会喜欢它,因为它能够封装 onError 回调上的错误处理,如下所示:

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,错误将会被捕获

Single.fromCallable{ getSomeData() /**🔥Crash🔥**/ }
   .map { item -> handleMap(item) } 
   .subscribe(
        { result -> handleResult(result) }, 
        { error -> handleError(error) } // Crash caught ✅
    )

Crashes 发生在 subscribe success 中

让我们来假设一下 Crashes 出现在 subscribe success 中,如下所示

Single.just(getSomeData() )
   .map { item -> handleMap(item) } 
   .subscribe(
        { result -> handleResult(result) /**🔥Crash🔥**/ }, 
        { error -> handleError(error) } // Crash NOT caught ⛔️ 
    )

这个错误将不会被 handleError 捕获,奇怪的是,如果我们将 Single 换成 Observable,异常就会被捕获,如下所示:

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。

3译者思考

作者大概总了 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。

参考文献
Beyond Basic RxJava Error Handling
RxJava 2 : Understanding Hot vs. Cold with just vs. fromCallable
What's different in 2.0

致力于分享一系列 Android 系统源码、逆向分析、算法、翻译相关的文章,目前正在翻译一系列欧美精选文章,请持续关注,期待与你一起成长。

https://github.com/hi-dhl/Android10-Source-Analysis
https://github.com/hi-dhl/AndroidX-Jetpack-Practice
https://github.com/hi-dhl/Leetcode-Solutions-with-Java-And-Kotlin

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342