笔记 | 初探Kotlin协程

如何理解协程

协程是一种不同于进程线程的存在,其本质是一种函数,同一线程中的多个协程是串行执行的,但为了理解仍然需要三者一起对比。

进程、线程、协程三者的上下文切换对比:

进程线程协程
切换者操作系统操作系统用户(编程者/应用程序)
切换时机根据操作系统自己的切换策略,用户不感知根据操作系统自己的切换策略,用户不感知用户自己(的程序)决定
切换内容页全局目录&内核栈&硬件上下文内核栈&硬件上下文硬件上下文
切换内容的保存保存于内核栈中保存于内核栈中保存于用户自己的变量(用户栈或者堆)
切换过程用户态 - 内核态 - 用户态用户态 - 内核态 - 用户态用户态(没有陷入内核态)
切换效率

从以上协程的特性中,可以知道协程的应用场景是I/O密集型任务,而非计算密集型任务

info: 我认为关于协程的全部,最佳参考为Google官方编写的文章:
谷歌开发者:在 Android 开发中使用协程 | 背景介绍

在安卓开发中使用协程

Kotlin 1.3版本中开始引入了一种全新处理并发的方式:协程,可以在 Android 平台上使用它来简化异步执行的代码,主要用来解决两个问题:

  1. 处理耗时任务 (Long running tasks),这种任务常常会阻塞住主线程;
  2. 保证主线程安全 (Main-safety) ,即确保安全地从主线程调用任何 suspend 函数。

在 Kotlin 中,所有协程都必须在调度器中运行,即使它们是在主线程上运行也是如此。suspend并不代表后台执行,在哪里执行由调度器决定。协程可以自行暂停,而调度器负责将其恢复。Kotlin提供了以下三种调度器并罗列了其使用场景:

调度器名称运行线程使用场景适用案例1适用案例2适用案例3
Dispatchers.MainAndroid上的主线程用来处理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 会跟踪它使用 launchasync 创建的所有协程。您可以随时调用 scope.cancel() 以取消正在进行的工作(即正在运行的协程)。在 Android 中,某些 KTX 库为某些生命周期类提供自己的 CoroutineScope。例如,ViewModelviewModelScopeLifecyclelifecycleScope。不过,与调度程序不同,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()
    }
}

最后修改于:2022年08月26日 12:00

添加新评论