初识Kotlin协程

协程可以让我们使用顺序的方式去写异步代码,而且不会阻塞UI线程。
Kotlin 协程提供了一种全新处理并发的方式,你可以在 Android 平台上使用它来简化异步执行的代码。协程从 Kotlin 1.3 版本开始引入,但这一概念在编程世界诞生的黎明之际就有了,最早使用协程的编程语言可以追溯到 1967 年的 Simula 语言。在过去几年间,协程这个概念发展势头迅猛,现已经被诸多主流编程语言采用,比如 Javascript、C#、Python、Ruby 以及 Go 等。Kotlin 协程是基于来自其他语言的既定概念。
Google 官方推荐将 Kotlin 协程作为在 Android 上进行异步编程的解决方案。

配置

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即协程作用域,用于对协程进行追踪。如果我们启动了多个协程但是没有一个可以对其进行统一管理的途径的话,就会导致我们的代码臃肿杂乱,甚至发生内存泄露或者任务泄露。为了确保所有的协程都会被追踪,Kotlin 不允许在没有CoroutineScope的情况下启动协程。
CoroutineScope 会跟踪所有协程,同样它还可以取消由它所启动的所有协程。这在 Android 开发中非常有用,比如它能够在用户离开界面时停止执行协程。
Coroutine 是轻量级的线程,并不意味着就不消耗系统资源。 当异步操作比较耗时的时候,或者当异步操作出现错误的时候,需要把这个 Coroutine 取消掉来释放系统资源。在 Android 环境中,通常每个界面(Activity、Fragment 等)启动的 Coroutine 只在该界面有意义,如果用户在等待 Coroutine 执行的时候退出了这个界面,则再继续执行这个 Coroutine 可能是没必要的。另外 Coroutine 也需要在适当的 context 中执行,否则会出现错误,比如在非 UI 线程去访问 View。 所以 Coroutine 在设计的时候,要求在一个范围(Scope)内执行,这样当这个 Scope 取消的时候,里面所有的子 Coroutine 也自动取消。所以要使用 Coroutine 必须要先创建一个对应的 CoroutineScope。

1、GlobalScope - 全局协程作用域

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

GlobalScope是一个单例实现,包含一个空的CoroutineContext,且不包含任何Job,该作用域常被拿来做示例代码。
GlobalScope 属于全局作用域,这意味着通过 GlobalScope 启动的协程的生命周期只受整个应用程序的生命周期的限制,只要整个应用程序还在运行且协程的任务还未结束,协程就可以一直运行,这可能会造成内存泄漏,因此不推荐使用。

2、MainScope - 主线程协程作用域

/**
 * 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)

用于返回一个CoroutineContext为SupervisorJob() + Dispatchers.Main的作用域,默认的调度模式为Main,也就是说该协程的线程环境是主线程。
该作用域常被使用在Activity/Fragment,并且在界面销毁时onDestroy()中要调用CoroutineScope.cancel()方法对协程进行取消,这是可以在开发中使用的一个用于获取作用域的顶层函数。

3、runBlocking

这是一个顶层函数,运行一个新的协程并且阻塞当前可中断的线程直至协程执行完成,该函数被设计用于桥接普通阻塞代码到以挂起风格(suspending style)编写的库,以用于主函数与测试。该函数主要用于测试,不适用于日常开发,该协程会阻塞当前线程直到协程体执行完成。

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
该扩展属性是 Android 的Lifecycle Ktx库提供的具有生命周期感知的协程作用域,用于返回一个CoroutineContext为SupervisorJob() + Dispatchers.Main.immediate的作用域,它与LifecycleOwner的Lifecycle绑定,Lifecycle被销毁时,此作用域将被取消。这是在Activity/Fragment中推荐使用的作用域,因为它会与当前的UI组件绑定生命周期,界面销毁时该协程作用域将被取消,不会造成协程泄漏。

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)
        )
    }

该扩展属性是ViewModel的扩展属性,和LifecycleOwner.lifecycleScope基本一致,也是返回一个CoroutineContext为SupervisorJob() + Dispatchers.Main.immediate的作用域,它能够在此ViewModel销毁时自动取消,同样不会造成协程泄漏。

6、使用 coroutineScope() 和 supervisorScope() 创建子作用域

这两个函数都是挂起函数,需要运行在协程内或挂起函数内。
coroutineScope和supervisorScope都会返回一个作用域,它俩的差别就是异常传播:coroutineScope 内部的异常会向上传播,子协程未捕获的异常会向上传递给父协程,任何一个子协程异常退出,会导致整体的退出;supervisorScope 内部的异常不会向上传播,一个子协程异常退出,不会影响父协程和兄弟协程的运行。
因此supervisorScope的设计应用场景多用于子协程为独立对等的任务实体的时候,比如一个下载器,每一个子协程都是一个下载任务,当一个下载任务异常时,它不应该影响其他的下载任务。

总结一下,关于作用域,更推荐的是在UI组件中使用LifecycleOwner.lifecycleScope,在ViewModel中使用ViewModel.viewModelScope


创建协程

常用的创建协程方式有两种:CoroutineScope.launch()和CoroutineScope.async()。
两者的区别在于返回值:launch创建新协程,但不将结果返回,返回一个Job,用于协程的监督与取消,适用于无返回值的场景。async创建新协程,返回一个Job的子类Deferred,可通过await()获取完成时返回值。async的返回值功能,需要与await()挂起函数结合使用。
此外两者之间的很大差异是它们对异常的处理方式不同:如果使用 async 作为最外层协程的开启方式,它期望最终是通过调用 await 来获取结果 (或者异常),所以默认情况下它不会抛出异常。这意味着如果使用 async 启动新的最外层协程,而不使用await,它会静默地将异常丢弃。

CoroutineScope.launch()

简单示例:

//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()

展示async的返回值功能,需要与await()挂起函数结合使用:

//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()
    }

展示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 挂起函数

Kotlin协程提供了 suspend 关键字,用于定义一个挂起函数,它就是一个标记,告知编译器,这个函数需在协程中执行,编译器会将挂起函数用有限状态机转换为一种优化版的回调。
挂起函数挂起协程的时候,并不会阻塞线程。
一个 suspend function 只能在一个协程或一个 suspend function 中使用。


CoroutineContext

CoroutineContext是Kotlin协程的一个基本结构单元。巧妙的运用协程上下文是至关重要的,以此来实现正确的线程行为、生命周期、异常以及调试。由如下几项构成:

  • Job: 控制协程的生命周期;
  • CoroutineDispatcher: 向合适的线程分发任务;
  • CoroutineName: 协程的名称,调试的时候很有用;
  • CoroutineExceptionHandler: 处理未被捕捉的异常。
    CoroutineContext 有两个非常重要的元素:Job 和 Dispatcher,Job 是当前的 Coroutine 实例而 Dispatcher 决定了当前 Coroutine 执行的线程,还可以添加CoroutineName,用于调试,添加 CoroutineExceptionHandler 用于捕获异常。
    简单示例:
val coroutineContext = Job() + Dispatchers.Default + CoroutineName("协程名字")
Log.d(TAG, "coroutineContext:$coroutineContext")

日志输出结果为:

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

Job

Job 用于处理协程。对于每一个所创建的协程 (通过 launch 或者 async),它会返回一个 Job实例,该实例是协程的唯一标识,并且负责管理协程的生命周期。
CoroutineScope.launch 函数返回的是一个 Job 对象,代表一个异步的任务。Job 具有生命周期并且可以取消。 Job 还可以有层级关系,一个Job可以包含多个子Job,当父Job被取消后,所有的子Job也会被自动取消;当子Job被取消或者出现异常后父Job也会被取消。
除了通过 CoroutineScope.launch 来创建Job对象之外,还可以通过 Job() 工厂方法来创建该对象。默认情况下,子Job的失败将会导致父Job被取消,这种默认的行为可以通过 SupervisorJob 来修改。
具有多个子 Job 的父Job 会等待所有子Job完成(或者取消)后,自己才会执行完成。

Job生命周期

Job生命周期包括New(新创建)、Active(活跃)、Completing(完成中)、 Completed(已完成)、Cancelling(取消中)、Cancelled(已取消)。虽然我们无法直接访问这些状态,但是我们可以访问 Job 的属性: isActive、isCancelled 和 isCompleted。

Job常用API

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

通过使用async创建协程可以得到一个有返回值Deferred,Deferred 接口继承自 Job 接口,额外提供了获取 Coroutine 返回结果的方法。由于 Deferred 继承自 Job 接口,所以 Job 相关的内容在 Deferred 上也是适用的。 Deferred 提供了额外三个函数来处理和Coroutine执行结果相关的操作。

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)

Job 是有父子关系的,如果子Job 失败了父Job会自动失败,这种默认的行为可能不是我们期望的。
而SupervisorJob就是这么一个特殊的 Job,里面的子Job不相互影响,一个子Job失败了,不影响其他子Job的执行。SupervisorJob(parent:Job?) 具有一个parent参数,如果指定了这个参数,则所返回的 Job 就是参数 parent 的子Job。如果 Parent Job 失败了或者取消了,则这个 Supervisor Job 也会被取消。当 Supervisor Job 被取消后,所有 Supervisor Job 的子Job也会被取消。
MainScope() 的实现就使用了 SupervisorJob 和一个 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)

协程异常处理

对异常的常规处理是使用try-catch,然而协程作用域内的异常无法使用try-catch取捕获,这个时候就需要使用CoroutineExceptionHandler来捕获协程作用域内的异常。
简单示例:

//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()
    }

日志输出结果:

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

在第二个协程中抛出的异常,使用CoroutineExceptionHandler就能捕获到异常了。
普通协程如果产生未处理异常会将此异常传播至它的父协程,然后父协程会取消所有的子协程、取消自己、将异常继续向上传递。
MainScope()因为它的实现是用了SupervisorJob,所以执行结果就是Child 2抛出异常后,但是不影响其他的Job,Child 3也就正常执行了。
使用supervisorScope也能达到类似SupervisorJob的效果,一个子协程异常退出,不会影响父协程和兄弟协程的运行。
简单示例:

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 ")
                }
            }
        }
    }

日志输出结果:

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

如果将supervisorScope换成coroutineScope,结果就不是这样了,Child 3就没有了:
日志输出结果:

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

CoroutineDispatcher

CoroutineDispatcher,调度器,定义了 Coroutine 执行的线程。CoroutineDispatcher 可以限定 Coroutine 在某一个线程执行、也可以分配到一个线程池来执行、也可以不限制其执行的线程。
CoroutineDispatcher 是一个抽象类,所有 dispatcher 都应该继承这个类来实现对应的功能。Dispatchers 是一个标准库中帮我们封装了切换线程的帮助类,可以简单理解为一个线程池。
Kotlin协程预置4种调度器:

  • Dispatchers.Default:默认的调度器,适合处理后台计算,是一个CPU密集型任务调度器。如果创建 Coroutine 的时候没有指定 dispatcher,则一般默认使用这个作为默认值。适合在主线程之外执行占用大量 CPU 资源的工作,示例包括对列表排序和解析 JSON。
    Default dispatcher 使用一个共享的后台线程池来运行里面的任务。注意它和Dispatchers.IO共享线程池,只不过限制了最大并发数不同。
  • Dispatchers.IO:用来执行阻塞 IO 操作的,适合执行IO相关操作,是一个IO密集型任务调度器。适合在主线程之外执行磁盘或网络 I/O,示例包括使用Room组件、从文件中读取数据或向文件中写入数据,以及运行任何网络操作。
    和Dispatchers.Default共用一个线程池来执行里面的任务。根据同时运行的任务数量,在需要的时候会创建额外的线程,当任务执行完毕后会释放不需要的线程。
  • Dispatchers.Main:根据平台不同会初始化为对应UI线程的调度器,如Android的主线程。此调度程序只能用于与界面交互和执行快速工作。示例包括调用 suspend 函数,运行 Android 界面框架操作,以及更新LiveData对象。
  • Dispatchers.Unconfined:不指定线程,所以执行的时候默认在启动线程。如果子协程切换线程,接下来的代码也在该线程继续执行。
    由于子协程会继承父协程的 context,所以为了方便使用,一般会在父协程上设定一个Dispatcher,然后所有子协程自动使用这个Dispatcher。

withContext

和launch、async及runBlocking不同,withContext不会创建新的协程,常用于切换协程执行所运行的线程。它也是一个挂起方法,直到结束返回结果。多个withContext是串行执行的, 所以很适合那种一个任务依赖上一个任务返回结果的情况。


ContinuationInterceptor

ContinuationInterceptor,拦截器,用来拦截协程做一些附加操作的,比如CoroutineDispatcher调度器,就是用拦截器实现的。
简单示例:打印日志的拦截器

//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)
        }
    }

日志输出结果:

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

发生了4次拦截,依次是:协程启动时(前两个),挂起时,返回结果时。
简单示例:线程调度器

//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)
        }
    }

日志输出结果:

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

参考文章:
万字长文 - Kotlin 协程进阶
Kotlin协程应用实战(硬核分享,一看就会)

猜你喜欢

转载自blog.csdn.net/yuantian_shenhai/article/details/124371396