跳至主要內容

提供上下文

guodongAndroid大约 3 分钟

前言

当涉及软件时,通常希望以抽象层级之间通信的方式来构建它。通过线程,抽象出在不同线程之间切换的方式非常有用。你可以通过抽象一个 线程提供器 来提供主线程和后台线程。 这与协程并没有什么不同。

由于线程机制是使用 CoroutineContexts 及其各自的 CoroutineDispatcher 实例进行抽象的,因此你可以构建一个提供器,用于委托每次构建协程时应使用哪个上下文。通常,这些提供器有一个声明的接口,它为你提供主线程和后台线程或调度器,因为这在具有用户界面的应用程序中很重要。

让我们看看如何构建这样的一个提供器。

构建上下文提供器

你已经了解了有哪些 CoroutineContext 对象以及它们的作用。要构建提供器,首先你必须定义一个接口,该接口提供一个通用上下文,你将在其上执行高昂的操作。注意:这个提供器不属于协程 API 的一部分,但是可以帮助我们抽象出主线程和后台线程的上下文。该接口看起来如下所示:

interface CoroutineContextProvider {
    fun context(): CoroutineContext
}

通过这种方式,你可以为每个你可能想到的特定用例构建不同的 CoroutineContextProviders。在具体的实现上,你可以在构造函数中要求传入 CoroutineContext,或者提取所需的信息到工厂函数中或者依赖注入,就像如下所示:

class CoroutineContextProviderImpl(
    private val context: CoroutineContext
) : CoroutineContextProvider {
    override fun context(): CoroutineContext = context
}

通过这种方式,每当你构建协程时,你可以使用这个上下文提供器。

不用担心这个类是否创建,这个类已经预定义好了。

现在,打开 Main.kt 文件,如下所示更改 main 函数:

fun main() {
    val parentJob = Job()
    val provider: CoroutineContextProvider = CoroutineContextProviderImpl(
        context = parentJob + Dispatchers.IO
    )

    GlobalScope.launch(context = provider.context()) {
        println(Thread.currentThread().name)
    }

    Thread.sleep(50)
}

然后确保导入提供的 CoroutineContextProviderCoroutineContextProviderImpl。在上面的代码片段中,首先你通过合并 parentJobDispatchers.IO 作为 CoroutineContext 构建了一个 CoroutineContextProvider。这将确保任何使用 provider.context() 启动的协程都拥有相同的父 Job 并都运行在后台线程中。

因此,你才能依赖于上下文的抽象提供器而不是手动编写你要使用的所有上下文。此外,当你构建提供器时,你可以传入任何您你要的上下文,从而有效地切换工作事件执行的线程池。如果你想使用主线程绑定上下文,可以使用如下所示的代码:

val mainContextProvider: CoroutineContextProvider = CoroutineContextProviderImpl(Dispatchers.Main)

你还可以再进一步,让上下文提供器不仅提供基于线程的上下文,还可以提供异常处理上下文和生命周期相关的上下文,或者干脆提供这些协程上下文元素的任意组合。

这在你想抽象出经常使用的上下文时非常有用。 此外,它还可以帮助你进行测试,你将在「第 13 章:测试协程」中看到这一点。