preface

  • Beyond Basic RxJava Error Handling
  • Original address: proandroiddev.com/beyond-basi…
  • Original author: Elye

Today, I saw an article by Elye about RxJava exception handling, which made me have a clear understanding of RxJava exception handling. I have used RxJava for a long time and have been ignorant of the exception handling mechanism inside.

Through this passage you will learn the following, which will be answered in the thinking section of the translator

  • What’s the difference between just and fromCallable?
  • What is RxJavaPlugins setErrorHandler?
  • Crashes occurred in just()?
  • Crashes processing scheme occurring in Subscribe Success?
  • Crashes occur in a SUBSCRIBE error?
  • Crashes occur in complete?
  • Do the Crashes occur before complete?

This article covers a lot of important points, so keep reading and you’ll learn a lot of tips.

The translation

Most people who know RxJava will like it because it encapsulates error handling on the onError callback, as shown below:

Single.just(getSomeData())
 .map { item -> handleMap(item) } 
 .subscribe(
        { result -> handleResult(result) }, 
        { error -> handleError(error) } // Expect all error capture 
    )
Copy the code

You would think that all Crashes would be handled by a call to handleError, but this is not always the case

Crashes occur in just()

I’ll start with a simple example, assuming that the crashes occur inside the getSomeData() method

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

This error will not be caught in handleError because just() is not part of the RxJava call chain. If you want to catch it, you may need to add a try-catch on the outermost layer to handle it, as shown below:

Just (getSomeData() /**🔥Crash🔥**/).map {item -> handleMap(item)}.subscribe({result -> HandleResult (result)}, {error -> handleError(error)} // Crash NOT caught ⛔️)} catch (exception: Exception) {handleError(Exception) // Crash caught ✅}Copy the code

If you use something inside RxJava, such as fromCallable, instead of just, the error will be caught

Single.fromcallable {getSomeData() /**🔥Crash🔥**/}.map {item -> handleMap(item)}.subscribe({result -> HandleResult (result)}, {error -> handleError(error)} // Crash caught ✅Copy the code

Crashes occur in Subscribe Success

Let’s assume that the Crashes appear in Subscribe Success as shown below

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

This error will not be caught by handleError. Strangely, if we replace Single with Observable, the exception will be caught, as shown below:

Observable.just(getSomeData() ) .map { item -> handleMap(item) } .subscribe( { result -> handleResult(result) /**🔥Crash🔥**/}, {error -> handleError(error)}, // Crash caught ✅ {handleCompletion()})Copy the code

The reason is that a successful subscription in Single is considered a complete stream. Therefore, errors can no longer be caught. In Observable, onNext needs to be processed, so crash can still be captured. How do we solve this problem

Error handling, as before, uses try-catch at the outermost layer to catch the exception

try { Single.just(getSomeData()) .map { item -> handleMap(item) } .subscribe( { result -> HandleResult (result)/**🔥Crash🔥**/}, {error -> handleError(error)} // Crash NOT caught ⛔️)} catch (exception: {handleError(Exception) // Crash NOT caught ⛔️}Copy the code

However, the exception is not caught and the crash is still passed because RxJava handles the crash internally and does not pass it externally

A very strange way to do this, in subscribe successful, is to try catch

Single.just(getSomeData() ) .map { item -> handleMap(item) } .subscribe( { result -> try { handleResult(result) * * / / * * 🔥 Crash 🔥} the catch (exception: Exception) {handleError(Exception) // Crash caught ✅}}, {error -> handleError(error)}, // Crash NOT caught ⛔️)Copy the code

This catches the exception, but RxJava doesn’t know how to handle it

A better way

As mentioned above, we cannot catch exceptions in subscribe Successful with Single because it is considered a complete stream. A better way to handle this situation is to use the doOnSuccess method

Single.just(getSomeData()).map {item -> handleMap(item)}.doonsuccess {result -> handleResult(result) /*🔥Crash🔥*/ Subscribe ({/** REMOVE CODE **/}, {error -> handleError(error)} // Crash caught ✅)Copy the code

When we do this, errors will be caught by onError. If we want to make our code look better, we can use the doOnError method, as shown below:

Single.just(getSomeData()).map {item -> handleMap(item)}.doonsuccess {result -> handleResult(result) /*🔥Crash🔥*/ Error -> handleError(error)} // Crash NOT stop ⛔️. Subscribe ()Copy the code

However, this does not completely solve the crash problem. Although it has been caught, it has not been stopped, so crashes still occur.

To be more precise, it actually does catch crash, but doOnError is not a complete state, so the error should still be handled in onError, otherwise it will crash inside, so we should at least provide an empty onError

Single.just(getSomeData()).map {item -> handleMap(item)}.doonsuccess {result -> handleResult(result) /*🔥Crash🔥*/ Error -> handleError(error)} // Crash NOT stop ⛔️. Subscribe ({} {}) // But Crash stop here ✅Copy the code

Crashes occur in a SUBSCRIBE error

Let’s consider what happens if the Crashes occur in a SUBSCRIBE error as follows:

Single.just(getSomeData() ) .map { item -> handleMap(item) } .subscribe( { result -> handleResult(result) }, {error -> handleError(error) /**🔥Crash🔥**/})Copy the code

We can think of using the method mentioned above to solve this problem

Single.just(getSomeData() ) .map { item -> handleMap(item) } .doOnSuccess { result -> handleResult(result) }, }.doonError {error -> handleError(error) /*🔥Crash🔥*/}.subscribe({} {}) // Crash stop here ✅Copy the code

Although this avoids crashes, it’s still weird because nothing is done while crashing. We can catch exceptions in onError in the following way, which is a very interesting way to program.

Single.just(getSomeData() ) .map { item -> handleMap(item) } .doOnSuccess { result -> handleResult(result) }, }.doonError {error -> handleError(error) /*🔥Crash🔥*/}.subscribe({} {error -> handleError(error)}) // Crash Caught ✅Copy the code

Anyway, it works, but I’m just showing you how to do it, and there’s a nice way to do it, right

Crashes occur in complete

An Observable, for example, has an onComplete state in addition to onError and onNext (and onSuccess like Single).

If the crashes occurred in onComplete as shown below, it will not be captured.

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

We can do this in the doOnComplete method as we did before, as follows:

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 ✅Copy the code

Eventually crash could be caught in doOnError and stopped at the last empty onError function we provided, but we got around the problem this way.

Crashes occur before complete

Let’s look at another interesting case, where we simulate a situation where our subscribed operation is too slow to terminate easily (if terminated, it crashes)

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)
Copy the code

We wait 1000 in fromCallable to complete, but at 100 milliseconds we terminate the operation by calling Disposem.dispose ().

This forces Thread.sleep (1000) to terminate before it finishes, causing it to crash and not be caught by doOnError or the provided onError function

Even if we were to use a try-catch on the outside, it would not work and would not work like all other RxJava internal crashes.

Try {val disposeMe = Observable. FromCallable {thread.sleep (1000)}. DoOnError {} // Crash NOT caught ⛔️. Subscribe ({}, {}, {}) // Crash NOT caught or stop ⛔️ Handler(). PostDelayed ({disposem.dispose ()}, 100)} catch (exception: {handleError(Exception) // Crash NOT caught too ⛔️}Copy the code

RxJava Crash ultimate solution

For RxJava, if a crash does occur, but a crash is out of your control, and you want to capture it in a global way, the following is a solution.

RxJavaPlugins.setErrorHandler { e -> handleError(e) }
Copy the code

Register ErrorHandler, which will catch all RxJava uncaught errors in any of the above cases (except just(), which is not part of the RxJava call chain)

Note, however, that the thread used to invoke error handling hangs where crash happened, and if you want to make sure it always happens on the main UI thread, include it with runOnUiThread{}

RxJavaPlugins.setErrorHandler { e -> 
    runOnUiThread { handleError(e))}
}
Copy the code

Therefore, in the case above, Crash is caused by terminating before completion, which is dealt with below.

RxJavaPlugins. SetErrorHandler {e - > handle (e)} / / Crash caught ✅ val disposeMe = observables. FromCallable { Thread.sleep(1000)}. DoOnError {error -> handleError(error)} // Crash NOT caught ⛔️. Subscribe ({}, {}, {}) // Crash NOT caught or stop ⛔ Handler().postdelayed ({disposem.dispose ()}, 100)Copy the code

Having this solution doesn’t mean that registering ErrorHandler is the right way to go

RxJavaPlugins.setErrorHandler { e -> handle(e) }
Copy the code

By understanding the above options for handling crashes, you can choose the most efficient solution and use multiple options together to more robustlyhandle crashes that occur in your application.

The translator think

The authors have outlined five possible locations for RxJava exceptions

  • Crashes occur in just()
  • Crashes occur in Subscribe Success
  • Crashes occur in a SUBSCRIBE error
  • Crashes occur in complete
  • Crashes occur before complete

In general, RxJava cannot determine which of these out-of-life, undeliverable exceptions should or should not cause the application to Crash. Finally, the author gives the ultimate solution to RxJava Crash, registering ErrorHandler

RxJavaPlugins.setErrorHandler { e -> handleError(e) }
Copy the code

It will capture all the above any case RxJava uncaught error, just (except), then we come to know about the RxJavaPlugins. SetErrorHandler

About RxJavaPlugins setErrorHandler

This is an important design of RxJava2.x. There are several types of errors that RxJava cannot catch:

  • An exception caused by the downstream life cycle having reached its terminal state
  • The downstream cancels the sequence that will issue the error and cannot issue the error
  • A crash occurred, but a crash was out of your control
  • Some third-party library code throws this exception when cancelled or interrupts are called, often resulting in an undeliverable exception.

RxJava cannot determine which of these out-of-life, undeliverable exceptions should or should not cause the application to crash.

These errors, which cannot be caught, are eventually sent to the rxJavaplugins.onerror handler. The handler can use the method RxJavaPlugins. SetErrorHandler () rewritten, RxJava will by default Throwable stacktrace print to the console, and calls the current thread uncaught exception handler.

So we can adopt a global approach, registered a RxJavaPlugins setErrorHandler (), add a non-empty global error handling, the following example illustrates several listed above can’t deliver.

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);
});
Copy the code

I believe that here about RxJava Crash processing scheme, should understand very clear, choose the most effective solution, multiple solutions used together, can be more robust processing program Crash occurred.

Let’s take a look at the difference between just and fromCallable. In another article, just-vs-fromcallable explains this in detail

Just and fromCallable

1. Values are obtained from different sources

The just value is obtained externally, while the fromCallable value is generated internally. To get a clearer picture, let’s look at the following code:

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) }
Copy the code

Subscribe 2 times for Just and fromCallable

From Just
801570614
801570614

From Callable
1251601849
2033593269
Copy the code

You’ll notice that no matter how many times you subscribe just, the generated random value remains the same, because the value is generated from outside the Observable, and the Observable just stores it for later use.

But for the fromCallable, which is generated from within an Observable, each subscribe generates a new random number.

2. Immediate execution and delayed execution

  • Just is generated immediately before the subscribe method is called.
  • FromCallable is executed after the subscribe method is called, which is deferred execution.

To get a clearer picture, let’s look at the following code:

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()
}
Copy the code

The result is as follows:

From Just
-Generating-
start subscribing
1778320787

From Callable
start subscribing
-Generating-
1729786515
Copy the code

For just Generating- before calling SUBSCRIBE, fromCallable is printing -generating – after calling subscribe.

This is the end of the article, let’s talk about a problem, in the Java era RxJava does help us solve a lot of problems, but relatively speaking, RxJava’s various operators, it is really difficult to understand, With Google making Kotlin the language of choice for Android, what are the benefits of RxKotlin?

reference

  • Beyond Basic RxJava Error Handling
  • RxJava 2 : Understanding Hot vs. Cold with just vs. fromCallable
  • What ‘s the company in 2.0

conclusion

Committed to sharing a series of Android system source code, reverse analysis, algorithm, translation related articles, is currently translating a series of European and American selected articles, please continue to pay attention to, in addition to translation and thinking about each European and American article, if it is helpful to you, please help me a like, thank you!! Looking forward to growing up with you.

The article lists

Android 10 source code series

  • How is APK generated
  • APK installation process
  • 0xA03 Android 10 source code analysis: APK loading process of resource loading
  • Android 10 source code: APK
  • Dialog loading and drawing process and use in Kotlin, DataBinding
  • WindowManager View binding and architecture

Android Apps

  • How to get video screenshots efficiently
  • How to package Kotlin + Android Databinding in your project
  • [Google engineers] just released a new Fragment feature, “New ways to transfer Data between Fragments” and source code analysis
  • [2.4K Start] Drop Dagger to Koin
  • [5K +] Kotlin’s performance optimization stuff
  • How does FragmentFactory elegantly use Koin and partial source code analysis

Tool series

  • Shortcuts to AndroidStudio that few people know
  • Shortcuts to AndroidStudio that few people know
  • All you need to know about ADB commands
  • 10 minutes introduction to Shell scripting

The reverse series

  • Dynamically debug APP based on Smali file Android Studio
  • The Android Device Monitor tool cannot be found in Android Studio 3.2