android Kotlin协程系列二

30 阅读7分钟

二 协程核心组件

2.1 什么是CoroutineScope(协程作用域)?它的核心作用是什么?如何自定义一个作用域?

它是协程的生命周期管理者,本质是一个持有CoroutineContext的对象,用于定义协程的运行边界(协程能在什么范围内启动,运行和销毁)。

核心作用: 协程生命周期绑定:确保所有启动的协程都能被统一管理(如取消),避免协程泄漏(如页面销毁后协程还在后台运行)。

上下文传递:通过自身持有的CoroutineContext,为启动的协程提供默认的调度器,异常处理器等配置。

public interface CoroutineScope {
    /**
     * The context of this scope.
     * Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
     * Accessing this property in general code is not recommended for any purposes except accessing the [Job] instance for advanced usages.
     *
     * By convention, should contain an instance of a [job][Job] to enforce structured concurrency.
     */
    public val coroutineContext: CoroutineContext
}

自定义CoroutineScope: 需要指定CoroutineContext(至少包含Job,用于控制生命周期)。

fun testMain() {
    //Job管理生命周期,Dispatchers指定默认线程
    val myContext = Job() + Dispatchers.IO
    //创建自定义Scope(持有上述上下文)
    val myScope = CoroutineScope(myContext)
    //使用:在自定义Scope中启动协程
    myScope.launch {
        delay(1000)
        println("运行在自定义Scope中")
    }
}

Job是Scope的核心,若Job被取消,整个Scope的所有协程都会被取消。

2.2 CoroutineContext(协程上下文)是什么?它包含哪些核心元素?Context与Scope的关系是怎样的?

CoroutineContext是协程的配置集合,本质是一组键值对Element的集合,用于存储协程运行所需的所有配置信息(如调度器,任务句柄,异常处理器等)。

核心元素:

Job:协程的任务句柄,控制协程的启动,取消,状态查询。

Dispather:决定协程运行在哪个线程。

CoroutineExceptionHandler:协程的异常处理器,统一捕获协程中的异常。

CoroutineName:协程的名称,用于调试(如日志中区分不谈协程)。

这些元素通过+运算符组合(若键相同,后面的元素会覆盖前面的):

val context= Job()+Dispatchers.Main+CoroutineName("MyCoroutine")

CoroutineContext与CoroutineScope的关系:

CoroutineScope持有CoroutineContext:本质上是对CoroutineContext的包装,CoroutineScope接口的核心字段就是CoroutineContext。

CoroutineScope是CoroutineContext的使用者:当通过CoroutineScope.launch启动协程时,协程会继承Scope的Context(也可以在启动时指定新的CoroutineContext覆盖)

简单说:Scope=Context+启动协程的方法(launch+async),Context是配置数据,Scope是使用这些数据管理协程的工具。

2.3 Job(协程任务)是什么?它有哪些状态?如何通过Job控制协程的启动,取消,等待?

Job是协程的任务句柄,代表一个正在运行或已完成的协程任务,通过它可以跟踪协程状态,控制协程生命周期。

核心状态(流转关系):

Job的状态是一个有限状态机,核心状态包括:

New:协程已创建但未启动(仅通过CoroutineStart.LAZY启动时会短暂处于此状态)。

Active:协程正在运行(大多数协程的主要状态)。

Completed:协程正常执行完毕(无异常)。

Cacelling:协程正在取消(处于取消过程中,资源释放阶段)。

Cancelled:协程已被取消(最终状态)。

转态流转图:New->Active->Completed或Active->Cancelling->Cancelled。

与Java的对比:Java的Thread也有状态(如RUNNABLE,TERMINATED),但Thread是内核级线程,状态由操作系统管理,而Job是用户态的任务状态,由协程runtime管理,更轻量且可控。

2.4 Dispather(协程调度器)的作用是什么?Kotlin提供了哪些默认Dispather?

Dispather是协程的线程分配器,核心作用是决定协程运行在哪个线程上,实现协程在不同线程间的切换。

Dispather.Main :运行在主线程(UI线程,仅Android/IOS有效)

Dispather.IO :运行在IO线程池(适合IO密集型任务)

Dispather.Default:运行在默认线程池(适合CPU密集型任务)

Dispather.UnConfined:不指定线程,在当前线程启动,暂停后可在任意线程恢复。

myScope.launch(Dispatchers.IO) {
    val data = fetchData() //在IO线程中执行
    withContext(Dispatchers.Main) {
        updateUI(data)//切换到Main线程
    }
    println("这是在IO线程中执行")
}

Deferred是什么?它的Job的区别是什么?为什么说Deferred是带返回值的Job?

Deferred是Job的子类,代表有返回值的协程任务,用于获取协程执行的结果,

Deferred: 有返回值(通过await获取),通过async创建,await()等待并获取结果。

Job:无返回值(仅表示任务完成),通过launch创建,join()等待完成。

为什么是带返回值的Job?

Deferred继承了Job的所有能力(如cancel(),join(),状态管理),因此可以像Job一样控制生命周期。

额外提供了await()方法:这是一个suspend函数,会暂停当前协程,知道Deferred任务完成,然后返回结果。

myScope.launch(Dispatchers.IO) {
    //启动一个带返回值的协程
    val deferred = async {
        delay(1000)
        "这是返回结果String"  //这是返回值
    }
    //获取结果,(非阻塞,仅暂停当前协程)
    val result = deferred.await()
    //1秒后打印 结果:这是返回结果String
    println("结果:   =$result")
}

Deferred.await()是暂停式的(当前协程暂停,线程可继续执行其他任务),效率更高。

2.5 什么是suspend函数?为什么suspend函数只能在协程或其他suspend函数中调用?它和Java中的普通方法有本质区别吗?

suspend(暂停)函数是被Kotlin编译器标记为可暂停执行的特殊函数,它可以在执行过程中暂停,让出线程给其他任务,稍后再从暂停点恢复执行。

为什么只能在协程或其他suspend 函数中调用?

suspend函数的暂停-恢复依赖协程的状态机和上下文(如保存局部变量,记录执行位置),而这些机制只能在协程内部才存在,协程本质就一个转态机,能保存暂停时的状态,普通函数(非协程,非suspend)运行在线程执行流中,没有转态机支持,无法处理暂停-恢复,因此,Kotlin编译器会强制检查,若在非协程/非suspend函数中调用suspend函数,会直接报错。

2.6 suspend函数的底层实现原理是什么?Kotlin编译器对suspend函数做了哪些特殊处理?

suspend函数的暂停-恢复能力并非JVM原生支持,而是Kotlin编译器通过CPS转换和状态机生成实现的,核心是将函数拆分为多个可断续执行的片段。

挂起函数,只是比普通的函数多了suspend关键字。Kotlin编译器就会特殊对待这个函数,将其转换成一个带有Callback的函数,这里的Callback就是Continuation接口。类似Java中的Callback但更通用(保存状态+后续逻辑)。这个过程,我们称之为CPS转换。

//原suspend函数
suspend fun fetchData():String{
    delay(1000)
    return "data"
}

//编译器转换后伪代码
fun fetchData(context: Continuation<String>):Any{
    return "包含暂停和恢复逻辑"
}

状态机生成: 若suspend函数内部有多个暂停点(如多次调用suspend函数),编译器会将函数拆分为多个状态,每个状态对应一个暂停点前后的逻辑,例如:

suspend fun doSomething() {
    step1()
    step2()
}

private fun step2() {
}

private fun step1() {
}

编译器会生成一个状态机,包含State.Initial(执行step1前),State.AfterStep1(step1完成后,执行step2前)等状态,通过状态流转实现暂停-恢复。

暂停与恢复:当执行到suspend函数(如delay)时,函数会保存当前状态到Continuation,然后返回一个特殊标记(如COROUTINE_SUSPENDED),通知调度器协程已暂停,当暂停条件已满足(如delay时间到),调度器会调用Continuation.resume(),从保存的状态继续执行后续逻辑。

与Java回调的对比:Java中实现分步执行需要手动写回调嵌套(如step1(new Callback(){onComplete(()->step2())})),而Kotlin通过编译器自动生成状态机,让开发者能用同步代码的写法实现异步逻辑,大幅简化代码。