笔记 | 初探Kotlin协程
warning: 这篇文章距离上次修改已过835天,其中的内容可能已经有所变动。
如何理解协程
协程是一种不同于进程
和线程
的存在,其本质是一种函数,同一线程中的多个协程是串行执行的,但为了理解仍然需要三者一起对比。
进程、线程、协程三者的上下文切换对比:
进程 | 线程 | 协程 | |
---|---|---|---|
切换者 | 操作系统 | 操作系统 | 用户(编程者/应用程序) |
切换时机 | 根据操作系统自己的切换策略,用户不感知 | 根据操作系统自己的切换策略,用户不感知 | 用户自己(的程序)决定 |
切换内容 | 页全局目录&内核栈&硬件上下文 | 内核栈&硬件上下文 | 硬件上下文 |
切换内容的保存 | 保存于内核栈中 | 保存于内核栈中 | 保存于用户自己的变量(用户栈或者堆) |
切换过程 | 用户态 - 内核态 - 用户态 | 用户态 - 内核态 - 用户态 | 用户态(没有陷入内核态) |
切换效率 | 低 | 中 | 高 |
从以上协程的特性中,可以知道协程的应用场景是I/O密集型任务
,而非计算密集型任务
。
在安卓开发中使用协程
Kotlin 1.3
版本中开始引入了一种全新处理并发的方式:协程,可以在 Android 平台上使用它来简化异步执行的代码,主要用来解决两个问题:
- 处理耗时任务 (Long running tasks),这种任务常常会阻塞住主线程;
- 保证主线程安全 (Main-safety) ,即确保安全地从主线程调用任何 suspend 函数。
在 Kotlin 中,所有协程都必须在调度器中运行,即使它们是在主线程上运行也是如此。suspend
并不代表后台执行,在哪里执行由调度器决定。协程可以自行暂停,而调度器负责将其恢复。Kotlin提供了以下三种调度器并罗列了其使用场景:
调度器名称 | 运行线程 | 使用场景 | 适用案例1 | 适用案例2 | 适用案例3 |
---|---|---|---|---|---|
Dispatchers.Main | Android上的主线程 | 用来处理UI交互和一些轻量级任务 | 调用suspend函数 | 调用UI函数 | 更新LiveData |
Dispatchers.IO | 非主线程 | 专为磁盘和网络IO进行了优化 | 数据库 | 文件读写 | 网络处理 |
Dispatchers.Default | 非主线程 | 专为CPU密集型任务进行了优化 | 数组排序 | JSON数据解析 | 处理差异判断 |
下面是一个使用调度器的例子。在 get 方法的主体内,调用 withContext(Dispatchers.IO) 来创建一个在 IO 线程池中运行的块。放在该块内的任何代码都始终通过 IO 调度器执行。由于 withContext本身就是一个suspend函数,它会使用协程来保证主线程安全。
// Dispatchers.Main
suspend fun fetchDocs() {
// Dispatchers.Main
val result = get("developer.android.google.cn")
// Dispatchers.Main
show(result)
}
// Dispatchers.Main
suspend fun get(url: String) = withContext(Dispatchers.IO) {
// Dispatchers.IO
}
// Dispatchers.Main
为了更好地管理和使用协程,一般要指定协程上下文(CoroutineContext)和协程作用域(CoroutineScope)。CoroutineScope
会跟踪它使用 launch
或 async
创建的所有协程。您可以随时调用 scope.cancel()
以取消正在进行的工作(即正在运行的协程)。在 Android 中,某些 KTX 库为某些生命周期类提供自己的 CoroutineScope
。例如,ViewModel
有 viewModelScope
,Lifecycle
有 lifecycleScope
。不过,与调度程序不同,CoroutineScope
不运行协程。
class ExampleClass {
//异常处理
private val handler = CoroutineExceptionHandler { _, exception ->
println("------------BaseViewModel异常捕获--------------")
exception.printStackTrace()
}
protected val parentJob = SupervisorJob() + handler
//协程上下文
protected open val coroutineContext: CoroutineContext
get() = parentJob + Dispatchers.IO
//协程作用域
protected val scope = CoroutineScope(coroutineContext)
fun exampleMethod() {
// Starts a new coroutine within the scope
scope.launch {
// New coroutine that can call suspend functions
fetchDocs()
}
}
fun cleanUp() {
// Cancel the scope to cancel ongoing coroutines work
scope.cancel()
}
}