io.reactivex.exceptions.UndeliverableException: java.lang.InterruptedException at io.reactivex.plugins.RxJavaPlugins.onError(RxJavaPlugins.java:366) at io.reactivex.internal.operators.observable.ObservableFromCallable.subscribeActual(ObservableFromCallable.java:48) at io.reactivex.Observable.subscribe(Observable.java:11194) at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96) at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:463)
官方对这个预期外的异常解释如下:
The exception is there because RxJava 2 has the policy of NEVER allowing an onError call to be lost. It is either delivered downstream or thrown as a global UndeliverableException if the observable has already terminated. It is up to the creator of the Observable to ‘properly’ handle the case where the observable has ended and an Exception occurs.
RxJava can’t decide if your exception thrown from you callable is important or not, even if the sequence is cancelled. Indeed you have to install a global error consumer and decide yourself if that error is something your app should crash on or not. The current wisdom is that NullPointerExceptions, IllegalArgumentExceptions, IllegalStateExceptions are crash-worthy while IOExceptions, SocketExceptions and InterruptedExceptions are likely not, but depends on your actual app.
// If Java 8 lambdas are supported RxJavaPlugins.setErrorHandler(e -> { });
// If no Retrolambda or Jack RxJavaPlugins.setErrorHandler(Functions.<Throwable>emptyConsumer());
不建议基于 RxJava2 的第三方库在测试环境以外修改 error handler。(It is not advised intermediate libraries change the error handler outside their own testing environment.)
不幸的是,RxJava 不知道哪些对象处于生命周期结束状态(out-of-lifecycle),未被分发的异常是应该让应用 crash。而定位这类问题的根源又非常麻烦,尤其是问题来自链的底部(especially if they originate from a source and get routed to RxJavaPlugins.onError somewhere lower the chain) 却被路由到 RxJavaPlugins.onError 时。
此外,一些第三方库被 cancel() 或 dispose() 调用中断时会抛出异常,大部分时候这种中断会引起未分发的异常。RxJava 2.0.6 内部现在总是先对 Subscription/Disposable 执行 cancel 或 dispose,再 cancelling/disposing 一个 task 或 worker (这会导致目标线程出现中断,which causes the interrupt on the target thread)。
1 2 3 4 5 6 7 8 9 10
// in some library try { doSomethingBlockingly() } catch (InterruptedException ex) { // check if the interrupt is due to cancellation // if so, no need to signal the InterruptedException if (!disposable.isDisposed()) { observer.onError(ex); } }
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); });
@OverrideprotectedvoidsubscribeActual(Observer<? super Response<T>> observer) { // Since Call is a one-shot type, clone it for each new observer. Call<T> call = originalCall.clone(); CallDisposabledisposable=newCallDisposable(call); observer.onSubscribe(disposable); if (disposable.isDisposed()) { return; }
publicfinalclassRxJavaPlugins { @Nullable staticvolatile Consumer<? super Throwable> errorHandler;
publicstaticvoidsetErrorHandler(@Nullable Consumer<? super Throwable> handler) { if (lockdown) { thrownewIllegalStateException("Plugins can't be changed anymore"); } errorHandler = handler; } publicstaticvoidonError(@NonNull Throwable error) { Consumer<? super Throwable> f = errorHandler;
if (error == null) { error = newNullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); } else { if (!isBug(error)) { error = newUndeliverableException(error); } }
On desktop Java, this latter handler does nothing on an ExecutorService backed Scheduler and the application can keep running. However, Android is more strict and terminates the application in such uncaught exception cases.