coroutines
- 官方描述:协程通过将复杂性放入库来简化异步编程。程序的逻辑可以在协程中顺序地表达,而底层库会为我们解决其异步性。该库可以将用户代码的相关部分包装为回调、订阅相关事件、在不同线程(甚至不同机器)上调度执行,而代码则保持如同顺序执行一样简单。
- 协程很重要的一点就是当它挂起的时候,它不会阻塞其他线程。协程底层库也是异步处理阻塞任务,但是这些复杂的操作被底层库封装起来,协程代码的程序流是顺序的,不再需要一堆的回调函数,就像同步代码一样,也便于理解、调试和开发。它是可控的,线程的执行和结束是由操作系统调度的,而协程可以手动控制它的执行和结束。
用同步代码写出异步效果
引入 coroutines 包
1
2
3
4
5
6
7
8
9
10
11
12
13
// 核心库
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1'
//如果要使用 ViewModelScope 需要添加
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
//如果要使用 LifecycleScope,请使用 需要添加
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0'
关键类和方法
- GlobalScope:launch
- runBlocking
- launch:Job
- job.isActive
- job.isCancelled
- job.isCompleted
- job.cancel()
- jon.join()
- GlobalScope:async
- Deferred.await()
- withContext
- suspendCoroutine 可以在协程中解决回调问题
启动模式(CoroutineStart ;GlobalScope:launch的第二个参数)
启动模式 | 作用 |
---|---|
DEFAULT | 默认的模式,立即执行协程体 |
LAZY | 只有在需要的情况下运行 |
ATOMIC | 立即执行协程体,但在开始运行之前无法取消 |
UNDISPATCHED | 立即在当前线程执行协程体,直到第一个 suspend 调用 |
代码分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
val asyncTask = GlobalScope.async {
Log.e("coroutines", "EEE-${Thread.currentThread().name}")
delay(5000)
Log.e("coroutines", "FFF-${Thread.currentThread().name}")
}
fun mainThread() {
Log.e("coroutines", "000-${Thread.currentThread().name}")
GlobalScope.launch(Dispatchers.IO) {
Log.e("coroutines", "AAA-${Thread.currentThread().name}")
suspendFun()
Log.e("coroutines", "CCC-${Thread.currentThread().name}")
asyncTask.await()
Log.e("coroutines", "GGG-${Thread.currentThread().name}")
}
Log.e("coroutines", "DDD-${Thread.currentThread().name}")
}
suspend fun suspendFun() {
withContext(Dispatchers.Main) {
Log.e("coroutines", "BBB-${Thread.currentThread().name}")
}
}
/*
运行结果:
EEE-DefaultDispatcher-worker-1
000-main
DDD-main
AAA-DefaultDispatcher-worker-1
BBB-main
CCC-DefaultDispatcher-worker-2
FFF-DefaultDispatcher-worker-1
GGG-DefaultDispatcher-worker-1
*/
- 从 000 后面执行的是 DDD 得知launch并没有阻塞主线程执行,即使是改为GlobalScope.launch(Dispatchers.Main)得到的结果也是一样的;这个特性和 runBlocking 相反
- 从AAA BBB CCC的执行顺序可知,BBB 虽然和 AAA CCC 不在同一个线程执行顺序却是按照 AAA BBB CCC 的代码顺序执行的,这就是所谓的挂起函数
- AAA 和 CCC 虽然在代码中属于同一块代码,确是在不同的线程中执行的。
- AAA 和 CCC 可能在同一线程执行也可能在不同线程执行
实现原理
Continuation.resumeWith ——> 生成类.invokeSuspend -> 状态判断
- 线程
- 状态机 机制
1.5 为什么将 GlobalScope 设置为易碎的(DelicateCoroutinesApi)
因为 GlobalScope 不能通过 scope 作用域自动取消任务,只能手动通过 job 取消任务,在 Activity 中使用 GlobalScope 时,GlobalScope 的生命周期会比 Activity 更加长
怎么将回调转成挂起函数
异常处理
1
2
3
lifecycleScope.launch(Dispatchers.Main) {
throw IndexOutOfBoundsException()
}
上面情况程序会直接崩溃
1
2
3
4
5
6
val handler = CoroutineExceptionHandler { _, exception ->
println("Caught $exception")
}
lifecycleScope.launch(handler) {
throw IndexOutOfBoundsException()
}
上面代码,程序不会崩溃,异常信息通过 CoroutineExceptionHandler 输出。
默认线程
- GlobalScope 默认 work 线程
- lifecycleScope 默认 主线程