kotlin协程

简介

  • 协程通过将复杂性放入库来简化异步编程。程序的逻辑可以在协程中顺序地表达,而底层库会为我们解决其异步性。该库可以将用户代码的相关部分包装为回调、订阅相关事件、在不同线程(甚至不同机器!)上调度执行,而代码则保持如同顺序执行一样简单。

  • 协程是一种并发设计模式,您可以在Android平台上使用它来简化异步执行的代码

协程的启动方式

  • runBlocking:T启动一个新的协程并阻塞调用它的线程,直到里面的代码执行完毕,返回值是泛型T,就是你协程体中最后一行是什么类型,最终返回的是什么类型T就是什么类型。

  • launch:Job 启动一个协程但不会阻塞调用线程,必须要在协程作用域(CoroutineScope)中才能调用,返回值是一个Job。

  • async:Deferred<T> 启动一个协程但不会阻塞调用线程,必须要在协程作用域(CoroutineScope)中才能调用。以Deferred对象的形式返回协程任务。返回值泛型T同runBlocking类似都是协程体最后一行的类型。

job

Job我们可以认为他就是一个协程作业是通过CoroutineScope.launch生成的,同时它运行一个指定的代码块,并在该代码块完成时完成。我们可以通过isActiveisCompletedisCancelled来获取到Job的当前状态。Job的状态如下图所示,摘自官方文档:

协程的生命周期

State [isActive] [isCompleted] [isCancelled]
New (optional initial state) false false false
Active (default initial state) true false false
Completing (transient state) true false false
Cancelling (transient state) false false true
Cancelled (final state) false true true
Completed (final state) false true false

我们可以通过下图可以大概了解下一个协程作业从创建到完成或者取消

sql 复制代码
                                      wait children
+-----+ start  +--------+ complete   +-------------+  finish  +-----------+
| New | -----> | Active | ---------> | Completing  | -------> | Completed |
+-----+        +--------+            +-------------+          +-----------+
                 |  cancel / fail       |
                 |     +----------------+
                 |     |
                 V     V
             +------------+                           finish  +-----------+
             | Cancelling | --------------------------------> | Cancelled |
             +------------+                                   +-----------+

Deferred

Deferred继承自Job,我们可以把它看做一个带有返回值的Job

kotlin 复制代码
public interface Deferred<out T> : Job {
    //返回结果值,或者如果延迟被取消,则抛出相应的异常
    public suspend fun await(): T  
    public val onAwait: SelectClause1<T>
    public fun getCompleted(): T
    public fun getCompletionExceptionOrNull(): Throwable?
}

什么是作用域

协程作用域(Coroutine Scope)是协程运行的作用范围。launchasync都是CoroutineScope的扩展函数,CoroutineScope定义了新启动的协程作用范围,同时会继承了他的coroutineContext自动传播其所有的 elements和取消操作。换句话说,如果这个作用域销毁了,那么里面的协程也随之失效。就好比变量的作用域,

协程的基本用法

协程依赖

arduino 复制代码
// 协程核心库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0"

// 协程Android支持库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0"

三种启动方式

csharp 复制代码
runBlocking {
    Log.i(TAG,"这是runBlocking方法启动协程")
}

GlobalScope.launch {
    Log.i(TAG,"这是launch方法启动协程")
}

GlobalScope.async {
    Log.i(TAG,"这是async方法启动协程")
}

挂起函数

suspend是协程的关键字,表示这是一个挂起函数,每一个被suspend饰的方法只能在suspend方法或者在协程中调用

线程调度器

调度器它确定了相关的协程在哪个线程或哪些线程上执行。协程调度器可以将协程限制在一个特定的线程执行,或将它分派到一个线程池,亦或是让它不受限地运行。官方框架预置了4个调度器:

  • Default:默认调度器,CPU密集型任务调度器,适合处理后台计算。通常处理一些单纯的计算任务,或者执行时间较短任务。比如:Json的解析,数据计算等

  • IO:IO调度器,,IO密集型任务调度器,适合执行IO相关操作。比如:网络处理,数据库操作,文件操作等

  • Main:UI调度器, 即在主线程上执行,通常用于UI交互,刷新等

  • Unconfined:非受限调度器,又或者称为"无所谓"调度器,不要求协程执行在特定线程上。

withContext

withContext 。这个函数可以切换到指定的线程,并在闭包内的逻辑执行结束之后,自动把线程切回去继续执行,并且可以将闭包的执行结果返回

scss 复制代码
// 方法一,使用 runBlocking 顶层函数
runBlocking (Dispatchers.Main){
    val result = withContext(Dispatchers.IO){
        Log.i(TAG,"在IO线程中执行")
    }
    Log.i(TAG,"在主线程中执行")
}

协程的并发与同步

因为协程是采用就是并发的设计模式,这句话的大多数环境下是没有问题。但是这里需要注意,如果某个协程满足以下几点,那它里面的子协程将会是同步执行的:

  • 父协程的协程调度器是处于Dispatchers.Main情况下启动。
  • 同时子协程在不修改协程调度器下的情况下启动。

协程启动模式

CoroutineStart协程启动模式,是启动协程时需要传入的第二个参数。协程启动有4种:

  • DEFAULT 默认启动模式,我们可以称之为饿汉启动模式,因为协程创建后立即开始调度,虽然是立即调度,单不是立即执行,有可能在执行前被取消。

  • LAZY 懒汉启动模式,启动后并不会有任何调度行为,直到我们需要它执行的时候才会产生调度。也就是说只有我们主动的调用Jobstartjoin或者await等函数时才会开始调度。

  • ATOMIC 一样也是在协程创建后立即开始调度,但是它和DEFAULT模式有一点不一样,通过ATOMIC模式启动的协程执行到第一个挂起点之前是不响应cancel 取消操作的,ATOMIC一定要涉及到协程挂起后cancel 取消操作的时候才有意义。

  • UNDISPATCHED 协程在这种模式下会直接开始在当前线程下执行,直到运行到第一个挂起点。这听起来有点像 ATOMIC,不同之处在于UNDISPATCHED是不经过任何调度器就开始执行的,也就是直接在当前线程执行。在遇到挂起点之后的执行,将取决于挂起点本身的逻辑和协程上下文中的调度器。

异常处理

协程上下文中的CoroutineExceptionHandler。是协程上下文中的一个Element,用来捕获协程中未处理的异常

kotlin 复制代码
val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
    Log.d("exceptionHandler", "${coroutineContext[CoroutineName]} :$throwable")
}

GlobalScope.launch(CoroutineName("异常处理") + exceptionHandler){
    val job = launch{
        Log.d("${Thread.currentThread().name}","我要开始抛异常了" )
        throw NullPointerException("异常测试")
    }
    Log.d("${Thread.currentThread().name}", "end")
}

协程作用域

  • 顶级作用域 --> 没有父协程的协程所在的作用域称之为顶级作用域。如:GlobalScope

  • 协同作用域 --> 在协程中启动一个协程,新协程为所在协程的子协程。子协程所在的作用域默认为协同作用域。此时子协程抛出未捕获的异常时,会将异常传递给父协程处理,如果父协程被取消,则所有子协程同时也会被取消。

javascript 复制代码
GlobalScope.launch(CoroutineName("异常处理") + exceptionHandler + SupervisorJob()) {
    val job = launch {
        Log.d("${Thread.currentThread().name}", "我要开始抛异常了")
        throw NullPointerException("异常测试")
    }
    Log.d("${Thread.currentThread().name}", "end")
}
  • 主从作用域 官方称之为监督作用域。与协同作用域一致,区别在于该作用域下的协程取消操作的单向传播性,子协程的异常不会导致其它子协程取消。但是如果父协程被取消,则所有子协程同时也会被取消。,主从作用域使用supervisorScope
javascript 复制代码
GlobalScope.launch(CoroutineName("异常处理") + exceptionHandler + SupervisorJob()){
    supervisorScope{
        val job = launch{
            Log.d("${Thread.currentThread().name}","我要开始抛异常了" )
            throw NullPointerException("异常测试")
        }
    }
    Log.d("${Thread.currentThread().name}", "end")
}

参考文章

史上最详Android版kotlin协程入门进阶实战(一) - 掘金 (juejin.cn)

相关推荐
androidwork4 分钟前
Kotlin实现文件上传进度监听:RequestBody封装详解
android·开发语言·kotlin
雨白9 分钟前
从拍照到相册,安全高效地处理图片
android
androidwork11 分钟前
解析401 Token过期自动刷新机制:Kotlin全栈实现指南
android·kotlin
-SOLO-12 分钟前
使用Trace分析Android方法用时
android
yzpyzp21 分钟前
Android 的AppBarLayout 与LinearLayput的区别
android
爱装代码的小瓶子37 分钟前
字符操作函数续上
android·c语言·开发语言·数据结构·算法
用户20187928316738 分钟前
故事:你的“老式弹簧售货机”(Stack<E>)
android
用户2018792831671 小时前
环形快递传送带大冒险:ArrayDeque 的奇幻之旅
android
用户2018792831671 小时前
故事:你的“急急急快递站”(PriorityQueue<E>)
android
烈焰晴天1 小时前
新发布的一款使用ReactNative新架构加载Svga动画的开源插件[android/ios]
android·react native·ios