Getting to Know Kotlin Coroutines

Coroutines allow us to write asynchronous code in a sequential manner without blocking the UI thread.
Kotlin coroutines provide a new way to handle concurrency, and you can use it on the Android platform to simplify asynchronously executed code. Coroutines have been introduced since version 1.3 of Kotlin, but this concept has been around since the dawn of the programming world. The earliest programming language using coroutines can be traced back to the Simula language in 1967. In the past few years, the concept of coroutines has developed rapidly and has been adopted by many mainstream programming languages, such as Javascript, C#, Python, Ruby, and Go. Kotlin coroutines are based on established concepts from other languages.
Google officially recommends Kotlin coroutines as a solution for asynchronous programming on Android.

configuration

dependencies {
    // lifecycle对于协程的扩展封装
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1"
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.1"
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1"
    // 协程核心库
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0"
    // 协程Android支持库
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0"
}

CoroutineScope

CoroutineScope is the coroutine scope, which is used to track coroutines. If we start multiple coroutines but there is no way to manage them uniformly, it will cause our code to be bloated and messy, and even memory leaks or task leaks will occur. To ensure that all coroutines are tracked, Kotlin does not allow coroutines to be started without a CoroutineScope.
CoroutineScope keeps track of all coroutines, and likewise it can cancel all coroutines started by it. This is very useful in Android development, for example, it can stop the execution of the coroutine when the user leaves the interface.
Coroutine is a lightweight thread, which does not mean that it does not consume system resources. When the asynchronous operation is time-consuming, or when an error occurs in the asynchronous operation, the Coroutine needs to be canceled to release system resources. In the Android environment, the Coroutine started by each interface (Activity, Fragment, etc.) is only meaningful in the interface. If the user exits the interface while waiting for the Coroutine to execute, it may be unnecessary to continue executing the Coroutine. In addition, Coroutine also needs to be executed in an appropriate context, otherwise errors will occur, such as accessing View in a non-UI thread. Therefore, when Coroutine is designed, it is required to be executed within a scope (Scope), so that when the Scope is cancelled, all child Coroutines in it are automatically cancelled. So to use Coroutine, you must first create a corresponding CoroutineScope.

1. GlobalScope - global coroutine scope

public object GlobalScope : CoroutineScope {
    /**
     * Returns [EmptyCoroutineContext].
     */
    override val coroutineContext: CoroutineContext
        get() = EmptyCoroutineContext
}

GlobalScope is a singleton implementation that contains an empty CoroutineContext and does not contain any Job. This scope is often used as a sample code.
GlobalScope belongs to the global scope, which means that the life cycle of the coroutine started by GlobalScope is only limited by the life cycle of the entire application. As long as the entire application is still running and the task of the coroutine has not ended, the coroutine can always run, which may cause memory leaks and is therefore not recommended.

2. MainScope - main thread coroutine scope

/**
 * Creates the main [CoroutineScope] for UI components.
 *
 * Example of use:
 * ```
 * class MyAndroidActivity {
 *     private val scope = MainScope()
 *
 *     override fun onDestroy() {
 *         super.onDestroy()
 *         scope.cancel()
 *     }
 * }
 * ```
 *
 * The resulting scope has [SupervisorJob] and [Dispatchers.Main] context elements.
 * If you want to append additional elements to the main scope, use [CoroutineScope.plus] operator:
 * `val scope = MainScope() + CoroutineName("MyActivity")`.
 */
@Suppress("FunctionName")
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)

It is used to return a scope whose CoroutineContext is SupervisorJob() + Dispatchers.Main. The default scheduling mode is Main, which means that the thread environment of the coroutine is the main thread.
This scope is often used in Activity/Fragment, and when the interface is destroyed, the CoroutineScope.cancel() method is called in onDestroy() to cancel the coroutine. This is a top-level that can be used in development to obtain the scope function.

3、runBlocking

This is a top-level function that runs a new coroutine and blocks the currently interruptible thread until the coroutine execution completes. This function is designed to bridge common blocking code to libraries written in the suspending style, to use In the main function and test. This function is mainly used for testing, not for daily development. This coroutine will block the current thread until the execution of the coroutine body is completed.

4、LifecycleOwner.lifecycleScope

/**
 * [CoroutineScope] tied to this [LifecycleOwner]'s [Lifecycle].
 *
 * This scope will be cancelled when the [Lifecycle] is destroyed.
 *
 * This scope is bound to
 * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate].
 */
public val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
    get() = lifecycle.coroutineScope

SupervisorJob() + Dispatchers.Main.immediate
This extended attribute is a lifecycle-aware coroutine scope provided by Android's Lifecycle Ktx library, which is used to return a scope whose CoroutineContext is SupervisorJob() + Dispatchers.Main.immediate. It Binding to the Lifecycle of the LifecycleOwner, this scope will be canceled when the Lifecycle is destroyed. This is the recommended scope in Activity/Fragment, because it will bind the life cycle with the current UI component, and the scope of the coroutine will be canceled when the interface is destroyed, which will not cause a leak of the coroutine.

5、ViewModel.viewModelScope

/**
 * [CoroutineScope] tied to this [ViewModel].
 * This scope will be canceled when ViewModel will be cleared, i.e [ViewModel.onCleared] is called
 *
 * This scope is bound to
 * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
 */
public val ViewModel.viewModelScope: CoroutineScope
    get() {
        val scope: CoroutineScope? = this.getTag(JOB_KEY)
        if (scope != null) {
            return scope
        }
        return setTagIfAbsent(
            JOB_KEY,
            CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
        )
    }

This extension attribute is an extension attribute of ViewModel, which is basically the same as LifecycleOwner.lifecycleScope. It also returns a scope where CoroutineContext is SupervisorJob() + Dispatchers.Main.immediate. It can be automatically canceled when this ViewModel is destroyed, and it will not cause coroutines leakage.

6. Use coroutineScope() and supervisorScope() to create subscopes

These two functions are suspending functions and need to be run in a coroutine or suspending function.
Both coroutineScope and supervisorScope will return a scope. The difference between them is exception propagation: the exception inside coroutineScope will be propagated upward, and the uncaught exception of the child coroutine will be passed upward to the parent coroutine. Any abnormal exit of a child coroutine will cause Exit as a whole; the exception inside supervisorScope will not be propagated upwards, and a child coroutine exits abnormally, which will not affect the operation of the parent coroutine and sibling coroutines.
Therefore, the design and application scenarios of supervisorScope are mostly used when sub-coroutines are independent and equivalent task entities, such as a downloader, and each sub-coroutine is a download task. When a download task is abnormal, it should not affect other Download tasks.

To sum up, regarding scope, it is more recommended to use LifecycleOwner.lifecycleScope in UI components and ViewModel.viewModelScope in ViewModel


Create a coroutine

There are two common ways to create coroutines: CoroutineScope.launch() and CoroutineScope.async().
The difference between the two lies in the return value: launch creates a new coroutine, but does not return the result, and returns a Job, which is used for the supervision and cancellation of the coroutine, and is suitable for scenarios with no return value. async creates a new coroutine, returns a Deferred subclass of Job, and can obtain the return value when it is completed through await(). The return value function of async needs to be used in conjunction with the await() suspend function.
In addition, the big difference between the two is that they handle exceptions differently: if you use async as the opening method of the outermost coroutine, it expects to finally get the result (or exception) by calling await, so by default It doesn't throw an exception. This means that if you use async to start a new outermost coroutine without await, it will silently discard the exception.

CoroutineScope.launch()

Simple example:

//MainScope()获取一个协程作用域用于创建协程
private val mScope = MainScope()

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 创建一个默认参数的协程,其默认的调度模式为Main 也就是说该协程的线程环境是Main线程
        mScope.launch {
            // 这里就是协程体
            // 延迟1000毫秒  delay是一个挂起函数
            // 在这1000毫秒内该协程所处的线程不会阻塞
            // 协程将线程的执行权交出去,该线程该干嘛干嘛,到时间后会恢复至此继续向下执行
            Log.d(TAG, "launch开启第一个协程")
            delay(1000)
            Log.d(TAG, "launch开启第一个协程结束")
        }
        // 创建一个指定了调度模式的协程,该协程的运行线程为IO线程
        mScope.launch(Dispatchers.IO) {
            // 此处是IO线程模式
            Log.d(TAG, "launch开启第二个协程")
            delay(1000)
            //将协程所处的线程环境切换至指定的调度模式Main 
            //和launch、async及runBlocking不同,withContext不会创建新的协程,常用于切换代码执行所运行的线程。 
            //它也是一个挂起方法,直到结束返回结果。多个withContext是串行执行的, 所以很适合那种一个任务依赖上一个任务返回结果的情况
            withContext(Dispatchers.Main) {
                // 现在这里就是Main线程了  可以在此进行UI操作了
                Log.d(TAG, "切换至主线程")
            }
        }
    }

override fun onDestroy() {
        super.onDestroy()
        // 取消协程 防止协程泄漏  如果lifecycleScope或者viewModelScope则不需要手动取消
        mScope.cancel()
    }

CoroutineScope.async()

To demonstrate the return value function of async, it needs to be used in conjunction with the await() suspending function:

//MainScope()获取一个协程作用域用于创建协程
private val mScope = MainScope()

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mScope.launch {
            // 开启一个IO模式的线程,并返回一个Deferred,Deferred可以用来获取返回值
            // 代码执行到此处时会新开一个协程 然后去执行协程体,父协程的代码会接着往下走
            val deferred = async(Dispatchers.IO) {
                // 模拟耗时
                delay(2000)
                // 返回一个值
                return@async "获得返回值"
            }
            // 等待async执行完成获取返回值,此处并不会阻塞线程,而是挂起,将线程的执行权交出去
            // 等到async的协程体执行完毕后,会恢复协程继续往下执行
            val data = deferred.await()
            Log.d(TAG, "data:$data")
        }
    }

override fun onDestroy() {
        super.onDestroy()
        // 取消协程 防止协程泄漏  如果lifecycleScope或者viewModelScope则不需要手动取消
        mScope.cancel()
    }

Demonstrate the concurrency capabilities of async:

//MainScope()获取一个协程作用域用于创建协程
private val mScope = MainScope()

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mScope.launch {
            // 此处有一个需求  同时请求5个接口  并且将返回值拼接起来
            val job1 = async {
                // 请求1
                delay(5000)
                return@async "1"
            }
            val job2 = async {
                // 请求2
                delay(5000)
                return@async "2"
            }
            val job3 = async {
                // 请求3
                delay(5000)
                return@async "3"
            }
            val job4 = async {
                // 请求4
                delay(5000)
                return@async "4"
            }
            val job5 = async {
                // 请求5
                delay(5000)
                return@async "5"
            }
            // 代码执行到此处时,5个请求已经同时在执行了
            // 等待各job执行完 将结果合并
            Log.d(TAG, "async: ${job1.await()} ${job2.await()} ${job3.await()} ${job4.await()} ${job5.await()}"
            )
        }
    }

override fun onDestroy() {
        super.onDestroy()
        // 取消协程 防止协程泄漏  如果lifecycleScope或者viewModelScope则不需要手动取消
        mScope.cancel()
    }

suspend suspend function

The Kotlin coroutine provides the suspend keyword, which is used to define a suspend function, which is a tag that tells the compiler that this function needs to be executed in the coroutine, and the compiler will convert the suspend function into a finite state machine An optimized version of the callback.
When the suspend function suspends the coroutine, it does not block the thread.
A suspend function can only be used in a coroutine or a suspend function.


CoroutineContext

CoroutineContext is a basic structural unit of Kotlin coroutines. Smart use of coroutine context is crucial to achieve correct thread behavior, lifecycle, exceptions, and debugging. Consists of the following items:

  • Job: Control the life cycle of the coroutine;
  • CoroutineDispatcher: distribute tasks to appropriate threads;
  • CoroutineName: The name of the coroutine, which is useful when debugging;
  • CoroutineExceptionHandler: Handle uncaught exceptions.
    CoroutineContext has two very important elements: Job and Dispatcher. Job is the current Coroutine instance and Dispatcher determines the thread that the current Coroutine executes. You can also add CoroutineName for debugging and add CoroutineExceptionHandler for catching exceptions.
    Simple example:
val coroutineContext = Job() + Dispatchers.Default + CoroutineName("协程名字")
Log.d(TAG, "coroutineContext:$coroutineContext")

The log output is:

coroutineContext:[JobImpl{Active}@1f12c95, CoroutineName(协程名字), Dispatchers.Default]

Job

Job is used to process coroutines. For each coroutine created (via launch or async), it will return a Job instance, which is the unique identifier of the coroutine and is responsible for managing the life cycle of the coroutine.
The CoroutineScope.launch function returns a Job object, representing an asynchronous task. Jobs have a lifecycle and can be canceled. Jobs can also have a hierarchical relationship. A job can contain multiple child jobs. When the parent job is canceled, all child jobs will be automatically canceled; when the child job is canceled or an exception occurs, the parent job will also be canceled.
In addition to creating the Job object through CoroutineScope.launch, you can also create the object through the Job() factory method. By default, the failure of a child job will cause the parent job to be canceled. This default behavior can be modified through SupervisorJob.
A parent job with multiple sub-jobs waits for all sub-jobs to complete (or cancel) before executing itself.

Job life cycle

Job life cycle includes New (newly created), Active (active), Completing (completed), Completed (completed), Canceling (cancelling), Canceled (cancelled). While we can't access these states directly, we can access the properties of the Job: isActive, isCancelled, and isCompleted.

Commonly used APIs for jobs

isActive: Boolean    //是否存活
isCancelled: Boolean //是否取消
isCompleted: Boolean //是否完成
children: Sequence<Job> // 所有子Job
cancel()             // 取消协程
join()               // 堵塞当前线程直到协程执行完毕
cancelAndJoin()      // 两者结合,取消并等待协程完成
cancelChildren()     // 取消所有子协程,可传入CancellationException作为取消原因
attachChild(child: ChildJob) // 附加一个子协程到当前协程上
invokeOnCompletion(handler: CompletionHandler): DisposableHandle //给 Job 设置一个完成通知,当 Job 执行完成的时候会同步执行这个通知函数

Deferred

By using async to create a coroutine, you can get a Deferred with a return value. The Deferred interface inherits from the Job interface, and additionally provides a method for obtaining the result returned by Coroutine. Since Deferred inherits from the Job interface, the content related to Job is also applicable to Deferred. Deferred provides three additional functions to handle operations related to Coroutine execution results.

await() //用来等待这个Coroutine执行完毕并返回结果
getCompleted() //用来获取Coroutine执行的结果。如果Coroutine还没有执行完成则会抛出 IllegalStateException ,如果任务被取消了也会抛出对应的异常。所以在执行这个函数之前,可以通过 isCompleted 来判断一下当前任务是否执行完毕了。
getCompletionExceptionOrNull() //获取已完成状态的Coroutine异常信息,如果任务正常执行完成了,则不存在异常信息,返回null。如果还没有处于已完成状态,则调用该函数同样会抛出 IllegalStateException,可以通过 isCompleted 来判断一下当前任务是否执行完毕了。

SupervisorJob

/**
 * Creates a _supervisor_ job object in an active state.
 * Children of a supervisor job can fail independently of each other.
 * 
 * A failure or cancellation of a child does not cause the supervisor job to fail and does not affect its other children,
 * so a supervisor can implement a custom policy for handling failures of its children:
 *
 * * A failure of a child job that was created using [launch][CoroutineScope.launch] can be handled via [CoroutineExceptionHandler] in the context.
 * * A failure of a child job that was created using [async][CoroutineScope.async] can be handled via [Deferred.await] on the resulting deferred value.
 *
 * If [parent] job is specified, then this supervisor job becomes a child job of its parent and is cancelled when its
 * parent fails or is cancelled. All this supervisor's children are cancelled in this case, too. The invocation of
 * [cancel][Job.cancel] with exception (other than [CancellationException]) on this supervisor job also cancels parent.
 *
 * @param parent an optional parent job.
 */
@Suppress("FunctionName")
public fun SupervisorJob(parent: Job? = null) : CompletableJob = SupervisorJobImpl(parent)

Jobs have a parent-child relationship. If the child Job fails, the parent Job will fail automatically. This default behavior may not be what we expect.
The SupervisorJob is such a special job, the sub-jobs in it do not affect each other, and the failure of a sub-job does not affect the execution of other sub-jobs. SupervisorJob(parent:Job?) has a parent parameter. If this parameter is specified, the returned Job is the child Job of the parameter parent. If the Parent Job fails or is canceled, the Supervisor Job will also be cancelled. When the Supervisor Job is canceled, all the sub-jobs of the Supervisor Job will also be cancelled.
The implementation of MainScope() uses SupervisorJob and a Main Dispatcher:

/**
 * Creates the main [CoroutineScope] for UI components.
 *
 * Example of use:
 * ```
 * class MyAndroidActivity {
 *     private val scope = MainScope()
 *
 *     override fun onDestroy() {
 *         super.onDestroy()
 *         scope.cancel()
 *     }
 * }
 * ```
 *
 * The resulting scope has [SupervisorJob] and [Dispatchers.Main] context elements.
 * If you want to append additional elements to the main scope, use [CoroutineScope.plus] operator:
 * `val scope = MainScope() + CoroutineName("MyActivity")`.
 */
@Suppress("FunctionName")
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)

Coroutine exception handling

The normal handling of exceptions is to use try-catch. However, exceptions within the scope of the coroutine cannot be caught using try-catch. At this time, it is necessary to use CoroutineExceptionHandler to catch exceptions within the scope of the coroutine.
Simple example:

//MainScope()获取一个协程作用域用于创建协程
private val mScope = MainScope()

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mScope.launch(Dispatchers.Default) {
            delay(500)
            Log.e(TAG, "Child 1")
        }
        // 在Child 2添加了异常处理
        mScope.launch(Dispatchers.Default + CoroutineExceptionHandler { coroutineContext, throwable ->
            Log.e(TAG, "CoroutineExceptionHandler: $throwable")
        }) {
            delay(1000)
            Log.e(TAG, "Child 2")
            throw RuntimeException("--> RuntimeException <--")
        }
        mScope.launch(Dispatchers.Default) {
            delay(1500)
            Log.e(TAG, "Child 3")
        }
    }

override fun onDestroy() {
        super.onDestroy()
        // 取消协程 防止协程泄漏  如果lifecycleScope或者viewModelScope则不需要手动取消
        mScope.cancel()
    }

Log output results:

Child 1
Child 2
CoroutineExceptionHandler: java.lang.RuntimeException: --> RuntimeException <--
Child 3

The exception thrown in the second coroutine can be caught by using CoroutineExceptionHandler.
If an ordinary coroutine generates an unhandled exception, it will propagate the exception to its parent coroutine, and then the parent coroutine will cancel all child coroutines, cancel itself, and continue to pass the exception upwards.
MainScope() is implemented using SupervisorJob, so the execution result is that after Child 2 throws an exception, but does not affect other jobs, Child 3 will execute normally.
Using supervisorScope can also achieve an effect similar to SupervisorJob. A child coroutine exits abnormally, which will not affect the operation of the parent coroutine and sibling coroutines.
Simple example:

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val scope = CoroutineScope(Job() + Dispatchers.Default)
        //如果将supervisorScope换成coroutineScope,结果就不是这样了,Child 3就没有了
        scope.launch(CoroutineExceptionHandler { coroutineContext, throwable ->
            Log.e(TAG, "CoroutineExceptionHandler: $throwable")
        }) {
            supervisorScope {
                launch {
                    delay(500)
                    Log.e(TAG, "Child 1 ")
                }
                launch {
                    delay(1000)
                    Log.e(TAG, "Child 2 ")
                    throw  RuntimeException("--> RuntimeException <--")
                }
                launch {
                    delay(1500)
                    Log.e(TAG, "Child 3 ")
                }
            }
        }
    }

Log output results:

Child 1
Child 2
CoroutineExceptionHandler: java.lang.RuntimeException: --> RuntimeException <--
Child 3

If you replace supervisorScope with coroutineScope, the result is not like this, and Child 3 is gone:
the log output result:

Child 1
Child 2
CoroutineExceptionHandler: java.lang.RuntimeException: --> RuntimeException <--

CoroutineDispatcher

CoroutineDispatcher, the scheduler, defines the threads that Coroutine executes. CoroutineDispatcher can limit Coroutine to execute in a certain thread, or it can be assigned to a thread pool for execution, or it can not limit its execution thread.
CoroutineDispatcher is an abstract class, and all dispatchers should inherit from this class to implement corresponding functions. Dispatchers is a helper class in the standard library that helps us encapsulate thread switching, which can be simply understood as a thread pool.
Kotlin coroutine presets 4 schedulers:

  • Dispatchers.Default: The default scheduler, suitable for processing background calculations, is a CPU-intensive task scheduler. If no dispatcher is specified when creating a Coroutine, this is generally used as the default value by default. Good for doing CPU-intensive work off the main thread, examples include sorting lists and parsing JSON.
    The Default dispatcher uses a shared pool of background threads to run its tasks. Note that it shares the thread pool with Dispatchers.IO, but the maximum number of concurrency is different.
  • Dispatchers.IO: used to perform blocking IO operations, suitable for performing IO-related operations, is an IO-intensive task scheduler. Suitable for performing disk or network I/O outside of the main thread, examples include using Room components, reading data from or writing data to files, and running any network operations.
    Share a thread pool with Dispatchers.Default to execute the tasks inside. Depending on the number of tasks running at the same time, additional threads will be created when needed, and unneeded threads will be released when the tasks are completed.
  • Dispatchers.Main: Depending on the platform, it will be initialized to the dispatcher corresponding to the UI thread, such as the main thread of Android. This scheduler should only be used to interact with the interface and perform quick work. Examples include calling suspend functions, running Android interface framework operations, and updating LiveData objects.
  • Dispatchers.Unconfined: No thread is specified, so the thread is started by default during execution. If the sub-coroutine switches threads, the following code will also continue to execute in this thread.
    Since the child coroutine will inherit the context of the parent coroutine, for the convenience of use, a Dispatcher is generally set on the parent coroutine, and then all child coroutines automatically use this Dispatcher.

withContext

Unlike launch, async, and runBlocking, withContext does not create a new coroutine, and is often used to switch the thread on which the coroutine executes. It is also a suspending method until the end returns a result. Multiple withContexts are executed serially, so it is very suitable for the situation where a task depends on the result returned by the previous task.


ContinuationInterceptor

ContinuationInterceptor, an interceptor, is used to intercept coroutines to do some additional operations, such as the CoroutineDispatcher scheduler, which is implemented with interceptors.
Simple example: interceptor for printing logs

//MainScope()获取一个协程作用域用于创建协程
private val mScope = MainScope()

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mScope.launch(Dispatchers.IO) {
            launch(MyInterceptor()) {
                Log.d(TAG, "1")
                val deferred = async {
                    Log.d(TAG, "2")
                    delay(100)
                    Log.d(TAG, "3")
                    return@async "返回值"
                }
                Log.d(TAG, "4")
                val result = deferred.await()
                Log.d(TAG, "5:$result")
            }.join()
            Log.d(TAG, "6")
        }
    }

override fun onDestroy() {
        super.onDestroy()
        // 取消协程 防止协程泄漏  如果lifecycleScope或者viewModelScope则不需要手动取消
        mScope.cancel()
    }

class MyInterceptor : ContinuationInterceptor {
        override val key: CoroutineContext.Key<*>
            get() = ContinuationInterceptor

        override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
            return MyContinuation(continuation)
        }
    }

class MyContinuation<T>(val continuation: Continuation<T>) : Continuation<T> {
        override val context: CoroutineContext
            get() = continuation.context

        override fun resumeWith(result: Result<T>) {
            Log.d(TAG, "result:$result")
            continuation.resumeWith(result)
        }
    }

Log output results:

result:Success(kotlin.Unit)
1
result:Success(kotlin.Unit)
2
4
result:Success(kotlin.Unit)
3
result:Success(返回值)
5:返回值
6

Four interceptions occurred, in order: when the coroutine started (the first two), when it was suspended, and when the result was returned.
Simple Example: Thread Scheduler

//MainScope()获取一个协程作用域用于创建协程
private val mScope = MainScope()

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mScope.launch {
            Log.d(TAG, Thread.currentThread().name)
            withContext(MyInterceptor2()) {
                Log.d(TAG, Thread.currentThread().name)
                delay(100)
                Log.d(TAG, Thread.currentThread().name)
            }
            Log.d(TAG, Thread.currentThread().name)
        }
    }

override fun onDestroy() {
        super.onDestroy()
        // 取消协程 防止协程泄漏  如果lifecycleScope或者viewModelScope则不需要手动取消
        mScope.cancel()
    }

class MyInterceptor : ContinuationInterceptor {
        override val key: CoroutineContext.Key<*>
            get() = ContinuationInterceptor

        override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
            return MyContinuation(continuation)
        }
    }

class MyContinuation<T>(val continuation: Continuation<T>) : Continuation<T> {
        override val context: CoroutineContext
            get() = continuation.context

        override fun resumeWith(result: Result<T>) {
            Log.d(TAG, "result:$result")
            continuation.resumeWith(result)
        }
    }

Log output results:

main
result:Success(kotlin.Unit)
我的线程池
result:Success(kotlin.Unit)
我的线程池
main

Reference article:
Wanzi Longwen - Kotlin Coroutine Advanced
Kotlin Coroutine Application Combat (hard core sharing, you will know it at a glance)

おすすめ

転載: blog.csdn.net/yuantian_shenhai/article/details/124371396