kotlin协程2025 通俗易懂三部曲之上篇 用法全解

用了kt已经3年多,协程也一直在用,但是并没有好好的整体盘一遍。现在整个完全学习版,总算是又通畅了不少,加深了理解。
计划分成3篇完成讲解。

上篇,基本用法,全讲解,本文。
中篇, kotlin协程2025 通俗易懂三部曲之中篇 常用函数和并发处理
下篇,kotlin协程2025 通俗易懂三部曲之下篇 异常处理

1. Kotlin 协程概述

对于协程的底层实现是极其复杂庞大的,我们要先学会使用,再去领悟设计思路和原理。

  • 协程
    轻量级线程被称为“协程”(Coroutine)。
    底层实现,Kotlin 编译器会将挂起函数转换为状态机。当一个挂起函数被调用时,它会检查当前的状态,如果需要挂起,它会保存当前的状态(包括局部变量和调用栈),并在稍后恢复执行。
    Kotlin 协程通过提供轻量级、易于使用的异步编程模型,极大地简化了异步编程的复杂性。这意味着协程的开销非常小,可以快速创建和销毁,非常适合执行密集的异步任务。
    其实在JVM平台上,协程的实现,是尽量循环使用线程,我们将它称作一个优秀的多线程任务调度框架更为合适,实现减少回调地狱的目的。

  • 组成
    主要包括几大要素,协程作用域CorountineScope,他的参数协程上下文CorountineContext, 以及如何发起协程launch/async等。
    协程上下文又包括:Dispatchers线程模型,Job/SupervisorJobCoroutineExceptionHandler异常处理器等。
    发起协程主要包括:launch/async, 他们的参数也有上下文可以输入,以及CoroutineStart
    在协程块中可以变更的东西:如何并发,并发方式,异常处理不同,切线程模型等。
    接下来将一一讲解。

文中,我会给出标记**#最佳实践或者#建议**,以便大家写出高质量的,可维护的,团队模板化的代码。

2. CorountineScope (协程作用域)

它限制和控制协程的作用范围(生命周期)。通过调用cancel()函数,取消当前协程和所有子协程。

常用作用域为:GlobalScope,viewModelScope,lifecycleScope,MainScope(自行创建)

使用示例:

lifecycleScope.launch {
    ALogJ.t("test run1")
    withContext (Dispatchers.IO) { //切线程  withContext 会在下篇中讲解
        ALogJ.t("test run2")
    }
    ALogJ.t("test run3")
}
//输出:
//MainThread: test run1
//SubThread63: test run2
//MainThread: test run3

lifecycleScope.launch(Dispatchers.Default) {
    ALogJ.t("test run1")
}
//输出:
//SubThread64: test run1

不指定launch的具体参数的情况下运行如下:

scope运行线程是否阻塞当前线程是否阻塞当前协程生命周期
GlobalScope子线程永远不终结
MainScope主线程永远不终结,需手动调用cancel()
lifecycleScope&viewModelScope主线程在主线程中阻塞生命周期自动cancel()

可以通过launch(Dispatchers.IO)的方式来修改执行上下文;
也可以通过withContext(Dispatchers.IO)的方式在协程体内修改执行上下文。withContext会在下篇中讲解。

#建议
  • 尽量使用fragment/activity的lifecycleScope,或者ViewModel的viewModelScope, 他们会随着生命周期结束,自动取消没有完成的任务;
  • 在某些类中,如果你能把控它的初始化和销毁。则可以自定义MainScope,手动cancel(), 如下:
    class SomeClass {
       private laterinit var scope:CoroutineScope
       
       fun init() {
           scope = MainScope()
       }
    
    	fun destroy() {
    		scope.cancel()
    	}
    }
    
  • 在不理解Scope的创建之前,避免到处new MainScope(), CoroutineScope()。因为需要手动管理协程的取消,无法把控生命周期。

3. Dispatchers (执行线程环境)

可以指定协程的执行线程环境,强调的是线程的类别。

Dispatchers.IO(IO密集型) 比如网络请求,读取文件等;
Dispatchers.Default(cpu密集型) 比如排序算法,计算等;
Dispatchers.Main(主线程) 切回主线程更新UI;
Dispatchers.Unconfined : 构建完协程立刻在当前线程执行(很少使用)

Dispatchers.Main 通过主线程Handler的post执行;
Dispatchers.Main.immediate 会先检查Looper.myLooper() == Looper.getMainLooper()立刻直接执行,否则通过主线程Handler.post执行

4. CoroutineContext (协程上下文)

它是CoroutineScope的唯一成员,会传递给子Job或者子Scope。

包括如下参数,可以通过+号,直接拼接:

  • Job(): 一般可选的就是Job() 或者 SupervisorJob() (第6章节SupervisorJob vs Job详细介绍);
  • Dispatchers:强调的就是线程模型,选择你期待的线程,一般可选为IO,Default,Main;
  • ExceptionHandler:异常处理,参考【下篇】。
  • CoroutineName: 协程名字。

Demo:

private val subScope = CoroutineScope(Job() + Dispatchers.Default + CoroutineExceptionHandler { coroutineContext, throwable ->
    logd { "${throwable.message}" }
})

lifecycleScope的上下文是什么呢?
kotlin LifecycleCoroutineScopeImpl(this, SupervisorJob() + Dispatchers.Main.immediate)
可以看出,主要是包括SupervisorJob和Dispatchers主线程。

5. launch/async ( Job/Deferred )

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {

public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> {

可以通过launch创建出Job。async创建得到Deffered。
Job和Deferred二者他们都可以调用cancel函数。
使用async,调用deferred.await()将会挂起协程,如果不await,与launch无区别。

5.1 参数:context和start
  • CoroutineContext参数:可以修改执行的上下文,最常用的修改线程,也可以传入异常处理捕获。
  • CoroutineStart参数:
    • 优先使用 DEFAULT:满足大多数异步场景需求。
    • ‌谨慎使用 UNDISPATCHED:需确保协程体内无阻塞操作,避免主线程卡顿。
    • LAZY 适用场景:如延迟加载资源或按条件触发任务。
      val job = launch(start = CoroutineStart.LAZY) {
          println("需手动触发执行")
      }
      job.start() // 显式启动协程
      
5.2 返回值:Job/Deferred

全称为:可被取消的协程引用。通过CoroutineScope创建的协程,就是Job,协程的实例。
一个Job可以有多个子Job,即协程可以有子协程。Job可以cancel,也会结束内部创建的子Job。

Job:

isActive 协程是否存活(注意懒启动)
isCancelled 协程是否取消
isCompleted 协程是否完成
cancel() 取消协程
start() 启动协程
join() 阻塞等候协程完成
cancelAndJoin() 取消并等候协程完成
invokeOnCompletion( onCancelling: Boolean = false, invokeImmediately: Boolean = true, handler: CompletionHandler)监听协程的状态回调
attachChild(child: ChildJob) 附加一个子协程到当前协程上。

Deferred:

Deferred继承了Job,自然能调用Job的函数;
同时还有额外的函数:
await()阻塞等待结果

Demo:

mJob?.cancel()
var job = viewModelScope.launch {
    try {
        //挂起函数,withContext(Dispatchers.IO)切换线程执行
        val info = api.signUp(email, password)
        signData.safeSetValue(SignSuccess(info.toData...))
    } catch (e: Throwable) {
        signData.safeSetValue(SignError(...))
    }
}
mJob = job

async Demo:

lifecycleScope.launch {
    val deferred = async(Dispatchers.IO) {
        //指定运行到子线程
        Thread.sleep(3000)
        """ {"data":"request successfully."} """
    }
    ALogJ.t("运行在主线程")
    val data = deferred.await()
    ALogJ.t("运行在主线程得到结果 $data")
}
特性launchasync
返回值无(Job有(Deferred<T>
用途后台任务、无返回值的操作需要结果的异步任务
阻塞性非阻塞非阻塞(但 await() 可同步阻塞)
异常存储存储在 Deferredawait() 时抛出,继续向上抛立即向上抛,传播到父协程或处理器
错误处理CoroutineExceptionHandler
try-catch
通过 try-catch自身block 或 捕获await()

异常处理详情在【下篇】。

6. SupervisorJob() vs Job()

是Scope级别,申明协程作用域的上下文。
注意与【中篇】的coroutineScope & supervisorScope 2个挂起函数,功能相似,这是这2个是在协程块级别。

特性JobSupervisorJob
子协程异常影响子协程异常会取消父协程及其他子协程子协程异常仅影响自身,不传播
适合场景严格依赖的并行任务,
需要保证所有子协程要么全部成功,
要么全部失败
‌独立任务(如多个网络请求)
lifecycleScope,viewModelScope, MainScope属于此类

代码实例:

private val mScope = CoroutineScope(Job() + Dispatchers.Default + CoroutineExceptionHandler { coroutineContext, throwable ->
        logd { "${throwable.message}" }
    })

mScope.launch {
    logt { "Run1......" }
    delay(100)
    throw RuntimeException("Error Exception1")
}
mScope.launch {
    logt { "Run2......" }
    delay(200)
    logt { "Run2......over" }
}
mScope.launch {
    logt { "Run3......" }
    delay(300)
    logt { "Run3......over" }
}

//执行结果
//SubThread[58]: Run1......
//SubThread[58]: Run2......
//SubThread[58]: Run3......
//Error Exception2

//换成SupervisorJob()的执行结果
//SubThread[59]: Run2......
//SubThread[60]: Run3......
//SubThread[58]: Run1......
//Error Exception
//SubThread[58]: Run2......over
//SubThread[58]: Run3......over

上述代码中:
由于第一个协程抛出了异常,其他协程都会跟着被取消。
如果申明mScope的参数Job()->SupervisorJob(),则是单个协程异常被捕获,不会导致其他协程被取消。
注意:不论哪种,申明的Scope没有CoroutineExceptionHandler,则都会抛向线程异常,进而app崩溃。

【下篇】会继续再次深入讲解Job()/SupervisorJob()的异常处理。

7. 总结

本文是Kotlin协程系列教程的上篇,主要介绍协程的基本用法。内容包括:协程的概念,核心组成要素(CoroutineScope作用域、CoroutineContext上下文、launch/async启动方式等),以及最佳实践建议。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值