Because the article involves only a lot of points, the content may be too long, according to their own ability level and familiarity with the stage jump read. If there is an incorrect place to tell you trouble private letter to the author, very grateful

Due to time reasons, the author works during the day and can only write in the free time at night, so the update frequency should be once a week. Of course, I will try my best to make use of time and try to publish in advance. In order to facilitate reading, this article is divided into multiple chapters, and the corresponding chapters are selected according to their own needs. Now it is only a general catalog in the author’s mind, and the final update shall prevail:

  • Basic usage of Kotlin coroutines
  • Kotlin coroutine introduction to Android version in detail (ii) -> Kotlin coroutine key knowledge points preliminary explanation
  • Kotlin coroutine exception handling
  • Use Kotlin coroutine to develop Android applications
  • Network request encapsulation for Kotlin coroutines
  • [Kotlin Coroutine for Android (6) -> Kotlin coroutine combined with Jetpack and the use of third-party frameworks]
  • [Kotlin coroutine introduction for Android (7) -> In-depth Kotlin Coroutine principle (1)]
  • [Kotlin coroutine introduction for Android version in detail (8) -> In-depth Kotlin coroutine principle (2)]
  • [Kotlin coroutine introduction for Android version in detail (9) -> In-depth Kotlin coroutine principle (3)]
  • [Kotlin coroutine introduction for Android (10) -> In-depth Kotlin coroutine principle (4)]

Extend the series

  • Encapsulating DataBinding saves you thousands of lines of code
  • A ViewModel wrapper for everyday use

Exception handling of kotlin coroutines

We mentioned in the last of this section will explain coroutines exception handling, but the author at the time of writing this article encountered some problems, mainly on the depth of the how to get to the point, because want to handle exceptions, must first know how abnormal happened, then inevitably involves coroutines create – > start – > perform – > scheduling – > recovery – > completed (cancelled) process. Each of these steps can list a lot of things that need to be explained, so the author finally decided that we will only look at the key points in this chapter, and we will only make a basic point, not an extension of the points involved in jumping out of the key points.

Of course, based on the feedback of the first two articles, some readers mentioned that the text and code information of the article is too much, and it is very tired to read from beginning to end. They want the author to arrange some pictures to relieve the tense learning atmosphere.

So I try to add some elements in this article, if there is inappropriate place, please kindly criticize and correct.

The process of generating coroutine exceptions

When we develop Android applications, an uncaught exception causes the application to exit. An uncaught exception on the same coroutine also causes the application to exit. If we want to handle an exception, we should first look at the process of generating an exception in the coroutine, and what information will appear when an uncaught exception occurs in the coroutine, as follows:

private fun testCoroutineExceptionHandler(a){
   GlobalScope.launch {
       val job = launch {
            Log.d("${Thread.currentThread().name}"."Throw an uncaught exception")
            throw NullPointerException("Exception test")
        }
       job.join()
       Log.d("${Thread.currentThread().name}"."end")}}Copy the code

We threw a NullPointerException but didn’t catch it, causing the application to crash and exit.

D/DefaultDispatcher-worker-2: Throws uncaught exceptions E/AndroidRuntime: FATAL EXCEPTION: defaultDispatcher-worker -1
    Process: com.carman.kotlin.coroutine, PID: 22734Java. Lang. NullPointerException: abnormal test at com. Carman. Kotlin. The coroutine. MainActivity $testException $1$job$1.invokeSuspend(MainActivity.kt:251)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
Copy the code

We see that this exception is generated in the CoroutineScheduler, although we don’t know what the CoroutineScheduler is. But we can start by analyzing the flow in terms of the names of the methods running on the log:

It creates a Worker object of CoroutineScheduler, runs the run method of the Worker object, calls executeTask with the runWorker method, and then executes runSafely in the executeTask. Then run DispatchedTask with runSafely, and finally call continuation with resumeWith. The resumeWith method throws an exception when invokeSuspend is executed.

Another one that’s a little more familiar, you should be able to get the idea. The employer asked the contractor Coroutine escheduler for a Worker and assigned him a DispatchedTask. At the same time, he told the Worker that he should runSafely carry bricks. And then the employer tells the Worker to do the runWorker, and the Worker starts executing the executeTask that the employer tells him to DispatchedTask, Finally, the invokeSuspend is executed via resumeWith to tell the employer that there is a problem (an exception is thrown).

Don’t worry, if you notice that this is similar to the ThreadPoolExecutor Thread pool and Thread Thread running. A contractor is like a ThreadPoolExecutor Thread pool, and workers are threads.

We create a Thread Thread (Worker) from the Thread pool (CoroutineScheduler) and start executing a Thread (runWorker). When executing a task we pass a try.. The runSafely command is executed to ensure that the task is safely executed. Then, when the DispatchedTask is executed, because the operation is abnormal, the resumeWith command is used to inform the result that the thread is out of order. Gee, the logic suddenly seems a lot clearer.

If you look at it this way, there is a fundamental principle behind the occurrence of this coroutine exception. So let’s see if it is as we thought. Let’s first find coroutine escheduler and see his implementation:

    internal class CoroutineScheduler(...). : Executor, Closeable {@JvmField
        val globalBlockingQueue = GlobalQueue()
        fun runSafely(task: Task) {
            try {
                task.run()
            } catch (e: Throwable) {
                val thread = Thread.currentThread()
                thread.uncaughtExceptionHandler.uncaughtException(thread, e)
            } finally {
                unTrackTask()
            }
        }
        
         / / to omit...
        internal inner class Worker private constructor() : Thread() {
            override fun run(a) = runWorker()
        
            private fun runWorker(a) {
                var rescanned = false
                while(! isTerminated && state ! = WorkerState.TERMINATED) {val task = findTask(mayHaveLocalTasks)
                    if(task ! =null) {
                        rescanned = false
                        minDelayUntilStealableTaskNs = 0L
                        executeTask(task)
                        continue
                    } else {
                        mayHaveLocalTasks = false
                    }
                    / / to omit...
                    continue}}private fun executeTask(task: Task) {
                / / to omit...
                runSafely(task)
               / / to omit...
            }

            fun findTask(scanLocalQueue: Boolean): Task? {
                if (tryAcquireCpuPermit()) return findAnyTask(scanLocalQueue)
                val task = if(scanLocalQueue) { localQueue.poll() ? : globalBlockingQueue.removeFirstOrNull() }else {
                    globalBlockingQueue.removeFirstOrNull()
                }
                returntask ? : trySteal(blockingOnly =true)}/ / to omit...
        }
        / / to omit...
    }
Copy the code

Oh, my gosh. That’s exactly what we thought. CoroutineScheduler inherits Executor, Worker inherits Thread, and runWorker is the Thread’s run method. At the runWorker executeTask(task) is executed; then in the executeTask call runSafely(task), we see that runSafely uses try.. A catch is used to execute the task and an uncaught exception is thrown in the catch. So clearly this task must be our DispatchedTask and that’s the end of it

Obviously not. We saw thatcatchIs thrown by a threaduncaughtExceptionHandlerWe are familiar with this, in Android development all through this crash message. But that’s clearly not our goal this time.

Moving on, let’s see if this task is a DispatchedTask. Back to the call to executeTask(Task), we see that the task was fetched from findTask, which in turn was fetched from the globalBlockingQueue in the CoroutineScheduler thread pool in findTask, Let’s look at this GlobalQueue:

internal class GlobalQueue : LockFreeTaskQueue<Task>(singleConsumer = false)
Copy the code
internal actual typealias SchedulerTask = Task
Copy the code

I can see that this queue is holding tasks, and I have given the Task a SchedulerTask alias via the Kotlin TypeAlias. DispatchedTask inherits from SchedulerTask, so the origin of DispatchedTask is explained.

internal abstract class DispatchedTask<in T>(
    @JvmField public var resumeMode: Int
) : SchedulerTask() {
 / / to omit...
 internal open fun getExceptionalResult(state: Any?).: Throwable? =
        (state as? CompletedExceptionally)? .causepublic final override fun run(a){ assert { resumeMode ! = MODE_UNINITIALIZED }val taskContext = this.taskContext
      var fatalException: Throwable? = null
    try {
            val delegate = delegate as DispatchedContinuation<T>
            val continuation = delegate.continuation
            withContinuationContext(continuation, delegate.countOrElement) {
                val context = continuation.context
                val state = takeState()
                val exception = getExceptionalResult(state)
                val job = if (exception == null && resumeMode.isCancellableMode) context[Job] else null
                if(job ! =null && !job.isActive) {
                    val cause = job.getCancellationException()
                    cancelCompletedResult(state, cause)
                    continuation.resumeWithStackTrace(cause)
                } else {
                    if(exception ! =null) {
                        continuation.resumeWithException(exception)
                    } else {
                        continuation.resume(getSuccessfulResult(state))
                    }
                }
            }
        } catch (e: Throwable) {
            fatalException = e
        } finally {
            val result = runCatching { taskContext.afterTask() }
            handleFatalException(fatalException, result.exceptionOrNull())
        }
    }
}
Copy the code

The DispatchedTask run method returns an exception via resumeWithException for a continuation when exception is not null. We mentioned continuations above. After a suspension of a suspended function, the continuation calls the resumeWith function to resume execution of the coroutine, returning success or failure of type Result

. ResumeWithException actually calls resumeWith, only it’s an extension function, only it only returns result.failure. At the same time, exceptions are thrown relentlessly by continuations.

public inline fun <T> Continuation<T>.resumeWithException(exception: Throwable): Unit =
    resumeWith(Result.failure(exception))
Copy the code

Oh, no, we haven’t implemented it hereinvokeSuspendAh, I think you’re wrong.

Yes, this is just a possibility. Let’s go back to where we called the continuation, which we got earlier with the DispatchedContinuation, In fact, the DispatchedContinuation is a BaseContinuationImpl object.

  val delegate = delegate as DispatchedContinuation<T>
  val continuation = delegate.continuation
Copy the code
internal abstract class BaseContinuationImpl(
    public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {
     public final override fun resumeWith(result: Result<Any? >) {
        var current = this
        var param = result
        while (true) {
            probeCoroutineResumed(current)
            with(current) {
                val completion = completion!! // fail fast when trying to resume continuation 
                valoutcome: Result<Any? > =try {
                        val outcome = invokeSuspend(param)
                        if (outcome === COROUTINE_SUSPENDED) return
                        Result.success(outcome)
                    } catch (exception: Throwable) {
                        Result.failure(exception)
                    }
                releaseIntercepted() // this state machine instance is terminating
                if (completion is BaseContinuationImpl) {
                    current = completion
                    param = outcome
                } else {
                    completion.resumeWith(outcome)
                    return
                }
            }
        }
    }
}
Copy the code

And you can see in the end this invokeSuspend is where we actually call our coroutine. Finally, a Continuation call to the resumeWith function resumes execution of the coroutine, returning a Result of type Result

. It’s the same as what we said above, except they’re in different stages.

HandleFatalException = handleFatalException = handleFatalException = handleFatalException = handleFatalException = handleFatalException = handleFatalException = handleFatalException = handleFatalException = handleFatalException;

HandleFatalException is mainly used to handle exceptions from the Kotlinx. coroutines library, so we have a brief overview here. There are two main types:

  1. kotlinx.coroutinesThe library or compiler has errors, resulting in internal error problems.
  2. ThreadContextElementThe coroutine context error is because we provided the incorrectThreadContextElementImplementation, causing the coroutine to be in an inconsistent state.
public interface ThreadContextElement<S> : CoroutineContext.Element {
    public fun updateThreadContext(context: CoroutineContext): S
    public fun restoreThreadContext(context: CoroutineContext, oldState: S)
}
Copy the code

We see that handleFatalException actually calls the handleCoroutineException method. HandleCoroutineException is a top-level function in the Kotlinx. coroutines library

public fun handleFatalException(exception: Throwable? , finallyException:Throwable?). {
    / / to omit...
    handleCoroutineException(this.delegate.context, reason)
}
Copy the code
public fun handleCoroutineException(context: CoroutineContext, exception: Throwable) {
    try{ context[CoroutineExceptionHandler]? .let { it.handleException(context, exception)return}}catch (t: Throwable) {
        handleCoroutineExceptionImpl(context, handlerException(exception, t))
        return
    }
    handleCoroutineExceptionImpl(context, exception)
}
Copy the code

We see handleCoroutineException will first take CoroutineExceptionHandler from coroutines context, if we don’t have defined CoroutineExceptionHandler words, It will call handleCoroutineExceptionImpl throws a uncaughtExceptionHandler lead to our program crashes exit.

internal actual fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) {
    for (handler in handlers) {
        try {
            handler.handleException(context, exception)
        } catch (t: Throwable) {
            val currentThread = Thread.currentThread()
            currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, handlerException(exception, t))
        }
    }
    val currentThread = Thread.currentThread()
    currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception)
}
Copy the code

I don’t know if you understand the above process, I was also here at the beginning of the back and forth. Around the dizzy. If you don’t understand, take a break, rub your eyes, pour a cup of hot water, and go back to your head.

All right, that’s it. Now that we have a general idea of how exceptions are thrown in kotlin coroutines, we won’t go any further. Let’s talk about exception handling.

Exception handling of coroutines

Kotlin coroutines exception handling, we are going to is divided into two parts, we know that the decomposition of the above, an exception is thrown by resumeWithException, there is an exception is thrown by CoroutineExceptionHandler directly, So let’s start talking about how to handle exceptions.

Try… try… try… try… If there is a crash, I try it first. I’m going to catch my program from crashing.

private fun testException(a){
    GlobalScope.launch{
        launch(start = CoroutineStart.UNDISPATCHED) {
            Log.d("${Thread.currentThread().name}"."I'm going to throw exceptions.")
            try {
                throw NullPointerException("Exception test")}catch (e: Exception) {
                e.printStackTrace()
            }
        }
        Log.d("${Thread.currentThread().name}"."end")}}Copy the code
D/DefaultDispatcher-worker-1: I'm going to start throwing the exception W/System. Err: Java. Lang. NullPointerException: abnormal test W/System. Err: at com.carman.kotlin.coroutine.MainActivity$testException$1$1.invokeSuspend(MainActivity.kt:252)
W/System.err:     at com.carman.kotlin.coroutine.MainActivity$testException$1$1.invoke(Unknown 
/ / to omit...
D/DefaultDispatcher-worker-1: end
Copy the code

Hey, our program didn’t crash at this point, just output a warning log. If you encounter a try.. What to do if you catch something, or if you miss something that needs to be tried… What about the location of the catch. Such as:

private fun testException(a){
    var a:MutableList<Int> = mutableListOf(1.2.3)
    GlobalScope.launch{
       launch {
            Log.d("${Thread.currentThread().name}"."I'm going to throw exceptions." )
            try {
                launch{
                    Log.d("${Thread.currentThread().name}"."${a[1]}")
                }
                a.clear()
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
        Log.d("${Thread.currentThread().name}"."end")}}Copy the code
D/ defaultDispatcher-worker-1: end D/ defaultDispatcher-worker-2: FATAL EXCEPTION: DefaultDispatcher-worker-2 Process: com.carman.kotlin.coroutine, PID: 5394 java.lang.IndexOutOfBoundsException: Index: 1, Size: 0 at java.util.ArrayList.get(ArrayList.java:437) at com.carman.kotlin.coroutine.MainActivity$testException$1$1$1.invokeSuspend(MainActivity.kt:252) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)Copy the code

When you think to use try.. When a catch would have caught, but it didn’t. This is because of our try.. We must use a try when we use a[1]. Catch. I always remember to use try.. Catch is good.

Yes, of course. But can you promise that you’ll remember it every time, that your fellow soldiers in the trenches will remember it. And when your logic is complex, you use so many tries… Catch if your code readability is much lower, can you still remember where exceptions might occur?

This time will need to use the coroutines CoroutineExceptionHandler in context. As we mentioned in our last article on coroutine context, it is an Element in the coroutine context that is used to catch unhandled exceptions in coroutines.

public interface CoroutineExceptionHandler : CoroutineContext.Element {

    public companion object Key : CoroutineContext.Key<CoroutineExceptionHandler>
    
    public fun handleException(context: CoroutineContext, exception: Throwable)
}
Copy the code

We modify it slightly:

private fun testException(a){
    val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
        Log.d("exceptionHandler"."${coroutineContext[CoroutineName]}$throwable")
    }
     GlobalScope.launch(CoroutineName("Exception Handling") + exceptionHandler){
         val job = launch{
             Log.d("${Thread.currentThread().name}"."I'm going to throw exceptions." )
             throw NullPointerException("Exception test")
         }
         Log.d("${Thread.currentThread().name}"."end")}}Copy the code
D/DefaultDispatcher-worker-1: I'm going to start throwing the exception D/exceptionHandler: CoroutineName (exception handling) : Java. Lang. NullPointerException: abnormal test D/DefaultDispatcher - worker -2: end
Copy the code

Even if we don’t use the try.. Catch to catch an exception, but the exception is caught and processed. It’s not that hard to handle paresthesia. Do we need to call a try… every time we start a coroutine? Add a CoroutineExceptionHandler catch all need it? At this point, we will see if you have really absorbed the previous knowledge:

  • The first one: you’ve digested the coroutine scope, so you can go through it or skip it. Because the rest of this is pretty much the same as what we’re talking about in coroutine scope.

  • Second: except the first, are the second. Then you’re gonna have to take a closer look.

We talked about exception passing earlier when we talked about cooperative scope and master-slave scope. Let’s first look at the synergy scope:

  • Synergistic scopeIf a child coroutine throws an uncaught exception, it passes the exception to the parent coroutine, and if the parent coroutine is cancelled, all child coroutines are cancelled at the same time.

Let me steal an official chart

By default, when a coroutine fails due to an exception, it propagates the exception to its parent, which cancels the remaining children and cancels its own execution. Finally, the exception is propagated to its parent. When the exception reaches the root of the current hierarchy, all coroutines started in the current coroutine scope are cancelled.

On the basis of a case before we make a little make a change, only the parent add CoroutineExceptionHandler coroutines, as usual, the code:

private fun testException(a){
    val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
        Log.d("exceptionHandler"."${coroutineContext[CoroutineName]}Handling exceptions:$throwable")
    }
    GlobalScope.launch(CoroutineName(Parent coroutine) + exceptionHandler){
        val job = launch(CoroutineName(Subcoroutine)) {
            Log.d("${Thread.currentThread().name}"."I'm going to throw exceptions." )
            for (index in 0.10.){
            launch(CoroutineName("Sun Tzu coroutine$index")) {
                Log.d("${Thread.currentThread().name}"."${coroutineContext[CoroutineName]}")}}throw NullPointerException("Null pointer exception")}for (index in 0.10.){
            launch(CoroutineName("Child coroutines$index")) {
                Log.d("${Thread.currentThread().name}"."${coroutineContext[CoroutineName]}")}}try {
            job.join()
        } catch (e: Exception) {
            e.printStackTrace()
        }
        Log.d("${Thread.currentThread().name}"."end")}}Copy the code
D/DefaultDispatcher-worker-3: I'm going to start throwing the exception W/System. Err: kotlinx. Coroutines. JobCancellationException: StandaloneCoroutineis cancelling; job=StandaloneCoroutine{Cancelling}@f6b7807
W/System.err: Caused by: Java. Lang. NullPointerException: null pointer exception W/System. Err: ats com. Carman. Kotlin. The coroutine. MainActivity $testException $1$job$1.invokeSuspend(MainActivity.kt:26/ / to omit...
D/DefaultDispatcher-worker-6Father: end D/exceptionHandler: CoroutineName (coroutines) handle exceptions: Java. Lang. NullPointerException: null pointer exceptionCopy the code

We see that the exception of the child coroutine job is handled by the parent coroutine, and no matter how many child coroutines I’m going to open to generate exceptions, it’s going to be handled by the parent coroutine. There is a problem, however, because exceptions can cause the parent coroutine to be canceled, and all subsequent child coroutines to not complete (and may occasionally run out). Then one might ask, what are the implications and application scenarios for this?

If you have a page that ends up presenting data that is concatenated by requesting data from multiple server interfaces, if one of these interfaces fails, the data will not be displayed, but the load will fail. Then you can use the above scheme to do, do not care who reported the fault, anyway, it is unified processing, once and for all. Examples like this should be common in development.

But there’s another problem. For example, the home page of our APP displays various data. For example: ads, pop-ups, unread status, list data and so on all exist on the home page, but they do not interfere with each other and do not relate to each other, even if one of them fails, it does not affect the display of other data. Then we can’t handle it with the plan above.

In this case, we can implement the master-slave (supervised) scope, which is consistent with the cooperative scope. The difference lies in the one-way propagation of the coroutine cancellation operation under this scope. The exception of a subcoroutine will not cause the cancellation of other subcoroutines. Let me steal another official picture:

When we talked about the master/slave scope, we said that to implement the master/slave scope you need to be able to use the supervisorScope or SupervisorJob. We should also add that we’re handling the container with the container is the container job. This is why the container is designed to look the same when handling the container as it is when handling the container with the coroutine.

/** * omit... * It is known that the container is designed for handling the container. * But overrides context's [Job] with [container]. * /
public suspend fun <R> supervisorScope(block: suspend CoroutineScope. () - >R): R {
   / / to omit...
}
Copy the code

It’s an excerpt from the official documentation, but the other thing I’m going to omit is this: “It’s going to cover the Job in context.” It’s the container we’re handling the orscope is the container we’re handling the orjob. We’ll be able to implement the following container we mentioned above with the container container container:

private fun testException(a){
    val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
        Log.d("exceptionHandler"."${coroutineContext[CoroutineName].toString()}Handling exceptions:$throwable")
    }
    GlobalScope.launch(exceptionHandler) {
        supervisorScope {
            launch(CoroutineName("Outlier subcoroutine")) {
                Log.d("${Thread.currentThread().name}"."I'm going to throw exceptions.")
                throw NullPointerException("Null pointer exception")}for (index in 0.10.) {
                launch(CoroutineName("Child coroutines$index")) {
                    Log.d("${Thread.currentThread().name}Normal execution"."$index")
                    if (index %3= =0) {throw NullPointerException("Child coroutines${index}Null pointer exception")}}}}}}Copy the code
D/DefaultDispatcher-worker-1: I'm going to start throwing the exception D/exceptionHandler: CoroutineName (abnormal child coroutines) handle exceptions: Java. Lang. NullPointerException: Null pointer exception D/DefaultDispatcher-worker-1Normal execution:1
D/DefaultDispatcher-worker-1Normal execution:2
D/DefaultDispatcher-worker-3Normal execution:0
D/DefaultDispatcher-worker-1Normal execution:3D/exceptionHandler: CoroutineName(Subcoroutine0) handle exceptions: Java. Lang. NullPointerException: coroutines0Null pointer exception D/exceptionHandler: CoroutineName(subcoroutine3) handle exceptions: Java. Lang. NullPointerException: coroutines3Null pointer exception D/DefaultDispatcher-worker-4Normal execution:4
D/DefaultDispatcher-worker-4Normal execution:5
D/DefaultDispatcher-worker-5Normal execution:7
D/DefaultDispatcher-worker-3Normal execution:6
D/DefaultDispatcher-worker-5Normal execution:8
D/DefaultDispatcher-worker-5Normal execution:9D/exceptionHandler: CoroutineName(Subcoroutine9) handle exceptions: Java. Lang. NullPointerException: coroutines9Null pointer exception D/exceptionHandler: CoroutineName(subcoroutine6) handle exceptions: Java. Lang. NullPointerException: coroutines6Null pointer exception D/DefaultDispatcher-worker-2Normal execution:10
Copy the code

You can see that even if more than one of these coroutines failed, we were able to get all the subcoroutines to complete. At this time, we can use such a scheme to solve the problem of refreshing multiple data on our home page without interfering with each other, and we can also deal with it uniformly when exceptions occur.

We are handling the container with the same code we are handling with the container.

private fun testException(a){
    val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
        Log.d("exceptionHandler"."${coroutineContext[CoroutineName].toString()}Handling exceptions:$throwable")}val supervisorScope = CoroutineScope(SupervisorJob() + exceptionHandler)
    with(supervisorScope) {
        launch(CoroutineName("Outlier subcoroutine")) {
            Log.d("${Thread.currentThread().name}"."I'm going to throw exceptions.")
            throw NullPointerException("Null pointer exception")}for (index in 0.10.) {
            launch(CoroutineName("Child coroutines$index")) {
                Log.d("${Thread.currentThread().name}Normal execution"."$index")
                if (index % 3= =0) {
                    throw NullPointerException("Child coroutines${index}Null pointer exception")}}}}}Copy the code

We’re going to be able to create a container container that we’re handling with the CoroutineScope, which is the container for the orjob, and then we’re going to be able to create the container container with the with container.

D/DefaultDispatcher-worker-1: I'm going to start throwing exceptions D/ defaultDispatcher-worker -2Normal execution:0D/exceptionHandler: CoroutineName(Subcoroutine0) handle exceptions: Java. Lang. NullPointerException: coroutines0Null pointer exception D/exceptionHandler: CoroutineName (abnormal child coroutines) handle exceptions: Java. Lang. NullPointerException: null pointer exception D/DefaultDispatcher - worker -2Normal execution:1
D/DefaultDispatcher-worker-2Normal execution:2
D/DefaultDispatcher-worker-4Normal execution:3D/exceptionHandler: CoroutineName(Subcoroutine3) handle exceptions: Java. Lang. NullPointerException: coroutines3Null pointer exception D/DefaultDispatcher-worker-1Normal execution:4
D/DefaultDispatcher-worker-4Normal execution:5
D/DefaultDispatcher-worker-4Normal execution:6D/exceptionHandler: CoroutineName(Subcoroutine6) handle exceptions: Java. Lang. NullPointerException: coroutines6Null pointer exception D/DefaultDispatcher-worker-4Normal execution:8
D/DefaultDispatcher-worker-3Normal execution:7
D/DefaultDispatcher-worker-2Normal execution:9D/exceptionHandler: CoroutineName(Subcoroutine9) handle exceptions: Java. Lang. NullPointerException: coroutines9Null pointer exception D/DefaultDispatcher-worker-3Normal execution:10
Copy the code

, of course, we are using coroutines, probably a collaborators cheng need to deal with their own exceptions, this time only need in the context of the collaborators cheng CoroutineExceptionHandler added before. After all, on demand, who knows what strange ideas a product might have.

Ok, so far we also have a basic understanding of the exception generation process in coroutines, and handling exceptions in coroutines as needed. If you are not clear, you can try it yourself or leave a message or a private message below. I will deal with it as soon as I see the news.

Notice and opinion gathering

In the next section, we will enter to the actual Android development, we will build a foundation first APP framework, method and request of some commonly used coroutines encapsulation way, as for the specific type of practical project, I want to ask everybody’s opinion, and then according to the actual situation of feedback to decide, welcome everybody to put forward opinions.

Finally: May everyone write a BUG so perfect that no test can find it.

Need source code to see here: Demo source

Originality is not easy. If you like this article, you can click “like”.

Related articles

  • Basic usage of Kotlin coroutines
  • Kotlin coroutine introduction to Android version in detail (ii) -> Kotlin coroutine key knowledge points preliminary explanation
  • Kotlin coroutine exception handling
  • Use Kotlin coroutine to develop Android applications
  • Network request encapsulation for Kotlin coroutines

Extend the series

  • Encapsulating DataBinding saves you thousands of lines of code
  • A ViewModel wrapper for everyday use