在之前的文章中,我们为开发者分享了Android中使用协程的一些基础知识,包括Android协程的背景介绍、入门指南和代码实战。本系列文章“协程中的取消和异常”也与Android协程有关。和大家一起探讨协程中取消操作和异常处理的知识点和技巧。当我们需要避免冗余处理以减少内存浪费和省电时,取消操作就显得尤为重要;而恰当的异常处理也是提升用户体验的关键。本文是另外两篇文章的基础(第二篇和第三篇会详细讲解协程的取消操作和异常处理),所以有必要对协程的一些核心概念进行讲解,比如CoroutineScope(协程作用域)、Job(task)和CoroutineContext(协程上下文),让我们可以更深入的学习。CoroutineScopeCoroutineScope跟踪您通过启动或异步创建的每个协程(这两个是CoroutineScope的扩展功能)。正在进行的工作(正在运行的协程)可以通过调用scope.cancel()随时取消。CoroutineScope:https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/启动:https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.htmlasync:https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html当你想在某个级别开启application或者控制协程的生命周期时,需要创建一个CoroutineScope。对于一些平台,比如Android,KTX等库已经在一些类的生命周期中提供了CoroutineScope,比如viewModelScope和lifecycleScope。viewModelScope:https://developer.android.google.cn/reference/kotlin/androidx/lifecycle/package-summary#(androidx.lifecycle.ViewModel).viewModelScope:kotlinx.coroutines.CoroutineScope生命周期范围:https://developer.android.google.cn/reference/kotlin/androidx/lifecycle/package-summary#lifecyclescope创建CoroutineScope时,会使用CoroutineContext作为构造函数的参数。你可以通过以下代码创建一个新的scope和coroutine://Job和Dispatcher已经集成到CoroutineContext中//我们稍后会详细介绍valscope=CoroutineScope(Job()+Dispatchers.Main)valjob=scope.launch{//新协程}JobJob用于处理协程。对于你创建的每个协程(通过launch或async),它都会返回一个Job实例,它是协程的唯一标识符,负责管理协程的生命周期。正如我们在上面看到的,您可以将Job实例传递给CoroutineScope以控制其生命周期。Job:https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.htmlCoroutineContextCoroutineContext是一组用于定义协程行为的元素。它由以下几项组成:Job:控制协程的生命周期;CoroutineDispatcher:将任务分发到合适的线程;CoroutineName:协程的名称,对调试有用;CoroutineExceptionHandler:处理未捕获的异常,在以后的第三篇文章中会详细讲解。CoroutineContext:thttps://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/-coroutine-context/index.html那么对于新建的协程,它的CoroutineContext是什么?我们已经知道一个Job的实例会被创建,这会帮助我们控制协程的生命周期。剩下的元素会继承自CoroutineContext的父类,它可能是另一个协程,也可能是创建该协程的CoroutineScope。由于CoroutineScope可以创建协程,并且你可以在协程内部创建更多的协程,所以里面有一个隐含的任务层次结构。在下面的代码片段中,除了通过CoroutineScope创建新的协程之外,让我们看看如何在协程中创建更多的协程:CoroutineScopeastheparentvalresult=async{//launch创建的新协程将使用当前协程作为父}。await()}层次结构的根通常是CoroutineScope。绘制层级图后如下图所示:△协程按照任务层级顺序执行。父级是CoroutineScope或其他协程Job的生命周期一个任务可以包含一系列状态:新建(New)、活动(Active)、完成(Completing)、完成(Completed)、取消(Cancelling)和完成Cancelled。虽然我们不能直接访问这些状态,但我们可以访问作业的属性:isActive、isCancelled和isCompleted。△Job生命周期如果协程处于活动状态,如果协程运行错误或调用job.cancel(),当前任务将被设置为取消(Cancelling)状态(isActive=false,isCancelled=true)。当所有子协程完成后,协程会进入取消(Cancelled)状态,此时isCompleted=true。解析父CoroutineContext在任务层次结构中,每个协程都会有一个父对象,或者是一个CoroutineScope,或者是另一个协程。但是,实际上协程的父CoroutineContext和父协程的CoroutineContext是不一样的,因为有下面这个公式:父上下文=默认值+继承的CoroutineContext+参数其中:部分元素包含默认值:Dispatchers。default是默认的CoroutineDispatcher,"coroutine"是默认的CoroutineName;继承的CoroutineContext为CoroutineScope或其父协程的CoroutineContext;传递给协程构建器的参数比继承的上下文参数具有更高的优先级,因此它们将覆盖相应的参数值。请注意:CoroutineContext可以使用“+”运算符组合。由于CoroutineContext是由一组元素组成的,加号右边的元素会覆盖加号左边的元素,形成新创建的CoroutineContext。例如,(Dispatchers.Main,"name")+(Dispatchers.IO)=(Dispatchers.IO,"name")。Dispatchers.IO:http://dispatchers.io/对于由CoroutineScope创建的每个协程,CoroutineContext将至少包含这些元素。这里的CoroutineName是灰色的,因为该值是从默认参数值派生的。所以现在我们明白了新协程的父CoroutineContext是什么样子的。它的实际CoroutineContext是:newCoroutineContext=parentCoroutineContext+Job()如果我们使用上图中的CoroutineScope,我们可以创建一个新的Coroutine如下:valjob=scope.launch(Dispatchers.IO){//newcoroutine}而协程的父CoroutineContext和它的实际CoroutineContext是什么?请看下图。CoroutineContext和父上下文中的Job不能通过一个实例,因为一个新的协程总是会得到一个新的Job实例。最终的父CoroutineContext将包含Dispatchers.IO而不是范围对象中的CoroutineDispatcher,因为它被协程构建器中的参数覆盖。另外注意父CoroutineContext中的Job是作用域对象(红色)的Job,新的Job实例(绿色)会被赋值给新协程的CoroutineContext。在我们系列的第三部分中,CoroutineScope将在其CoroutineContext中包含另一个名为SupervisorJob的Job实现,它改变了CoroutineScope处理异常的方式。因此,由这个作用域对象创建的新协程将有一个SupervisorJob作为它们的父Job。但是,当一个协程的父级是另一个协程时,父级的Job仍然是Job类型。现在,大家都了解了协程的一些基本概念。下一篇我们继续深入讨论第二篇协程的取消,第三篇讨论协程的异常处理。感兴趣的读者,请继续关注我们的更新。【本文为栏目组织《GoogleDevelopers》原创稿件,转载请联系原作者(微信公众号:Google_Developers)】点此查看更多本作者好文
