Kotlin 学习笔记(五)—— 协程的基础知识,面试官的最爱了~(上)

简介: Kotlin 学习笔记(五)—— 协程的基础知识,面试官的最爱了~(上)

又是一个月没见了,坚持永远是世上最难的事情,但,往往难事才会有更大的收获。与君共勉~

前段时间一直在学习 Compose,所以导致 Kotlin 笔记系列搁置了好久。一方面是因为 Compose 的学习在个人来看重要性更高;另一方面就是,发现学完之前的 Kotlin 系列的笔记一到笔记四后,已经基本可以在项目中使用 Kotlin 进行日常的编码了,所以才导致这个 Kotlin 学习笔记系列停更了好久,哈哈!对 Jetpack Compose 感兴趣的同学可以看一下我的另一个笔记系列—— Jetpack Compose 学习笔记。这次咱来看看 Kotlin 协程的基础知识。


1. 协程是什么


协程是一种编程思想。它并不局限于任何语言,不仅 Kotlin 中有对协程的实现,Python、Go 等语言也有。

更实际一点,协程的代码是运行在线程中的,可以在单线程中执行;也可以在多线程中执行,即支持来回切换。并且协程没有直接和操作系统关联,而是跟线程紧密关联,毕竟是要靠线程去执行。它的设计初衷就是为了解决并发问题,可以更方便地处理多线程协作的任务。

在 Kotlin 中,协程就是一个封装好的线程框架。类比于 Java 中的 Executor 或 Android 中的 AsyncTask。只要内存足够,一个线程可以运行任意多个协程,但在某一时刻只能有一个协程在运行,多个协程分享该线程分配到的计算机资源。下面图是进程、线程、协程之间的关系图:

image.png

这里是拿 Android 应用来举例的,其实不仅在 Android 中有 UI 主线程的概念,在 Go、Python 等支持协程的语言中,也有主线程的概念。有一点需要注意的是,进程是可以直接通过协程去处理一些事件的,只不过现在的编程语言都约定俗成地采用了图1 的这种层次。

所以,协程就是一套封装好的线程API框架,只不过使用起来非常方便,可以用看起来是同步的代码,去实现异步的操作。


2. suspend 关键字


先来看看 suspend 关键字,好像老是在协程的代码中碰到它。按照它的字面意思,就是“挂起”的意思,确实比较形象,因为被它修饰的方法,是可以被挂起的。这里被挂起的对象是这个方法所在的协程。那么,协程被挂起的真正意思是什么?

协程被挂起的意思是,这个正在线程上运行的协程体代码,将要从当前线程脱离开来,即剩下的协程代码不往下执行了。脱离开后,协程和线程会怎么样呢?线程这边比较好理解,如果有其他的任务需要处理,操作系统肯定会安排线程去执行其他的任务;如果暂时没有什么任务需要处理,可能就会被回收掉,或者放入线程池中;协程这一边,则将会在指定的线程中,继续执行之前被中断的代码。至于被指定的线程具体是哪个,是由 suspend 函数具体实现决定的。常见的可以调用 withContext 方法去指定线程。

suspend 关键字本身没有挂起的作用,需要在方法内部直接或者间接地调用 Kotlin 协程框架中的 suspend 函数才可以。所以,suspend 关键字更多的是给调用者一个提示,提示调用者被它修饰的方法是个耗时方法,需要在协程或者其他 suspend 函数中处理,限制这个方法只能在协程或其他 suspend 函数中被调用。

了解了 suspend 的用法,再来看看实际使用中,协程的几个组成部分。


3. 协程的几个重要组成部分


一般来说,协程的启动使用比较多的有如下的三个方法:

  1. runBlocking: T
  2. launch: Job
  3. async/await: Deferred

通过查看这三个方法,我们可以得知协程的几个重要的属性,或者是参数。拿 launch: Job的源码来说,我们从 GlobalScope.launch的 launch 方法进入,可以看到 launch 的方法声明是:

// code 1 launch 方法声明
public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job

其中的 CoroutineContextCoroutineStart suspend CoroutineScope.() -> Unit等几个参数都是协程的重要组成部分。


3.1 协程上下文


先看看 launch 方法的第一个参数—— CoroutineContext,协程上下文,跟 Android 里面的 Context 上下文类似,通常用于协程间切换时,传递参数的作用;还可以指定协程在哪个线程中执行,比如 IO 线程、UI Main 线程等;还可以指定当前协程中断后在哪个线程中去恢复它。

默认 CoroutineContext 是设置的 EmptyCoroutineContext —— 一个标准库已经定义好的 object,表示一个空的协程上下文,里面没有任何数据。而 CoroutineContext 的数据结构与集合类似,内部实现是一个单链表,里面的元素是 Element。

Element 中有一个属性 key,这个 key 就是元素 Element 的索引。要说协程上下文在我们的开发中如何使用,我找了下网上的一些资料,提到较多的就是异常的捕获了。如下面 code2 所示:

// code 2  设置协程的异常捕获
val coroutineExceptionHandler = CoroutineExceptionHandler { context, throwable ->
    // 出现异常则会执行
    throwable.printStackTrace()
}
GlobalScope.launch (Dispatchers.Main + coroutineExceptionHandler) {
    // 执行操作
}

这段代码用到了 GlobalScope.launch,代表是个顶级作用域的协程,不推荐在 Android 开发中使用,因为它的生命周期是与Application 应用一致的,所以容易造成内存泄漏。CoroutineExceptionHandler 可以让我们在启动协程时设置一个统一的异常处理器,如果出现异常,就会执行相应的操作。这里的上下文还设置了协程运行的线程为 Main 主线程。此外,CoroutineContext 重载了 plus 方法(public operator fun plus(context: CoroutineContext): CoroutineContext),所以可以直接使用加号 “+” 来添加一个 Element。

还有一个更为复杂的例子,也可以大致看出协程的组成:

// code 3  协程的内部组成
suspend {
    // 协程体
    Log.d(TAG, "CoroutineStart: +++++ ${coroutineContext[CoroutineName]}   ${Thread.currentThread().name}")
    // val tmp = 1/0
    "suspend 返回值"
}.startCoroutine(object : Continuation<String> {
    override val context: CoroutineContext
        get() = EmptyCoroutineContext + CoroutineName("修之竹") + CoroutineExceptionHandler { context, throwable ->
            Log.d(TAG, "CoroutineExceptionHandler: ++++ ${throwable}")
        }
    override fun resumeWith(result: Result<String>) {
        // 协程执行完会执行 resumeWith 方法
        Log.d(TAG, "resumeWith: ++++ CoroutineEnd")
        result.onSuccess {
            Log.d(TAG, "++++++ resumeWith onSuccess: ${context[CoroutineName]?.name}")
        }
        result.onFailure {
            Log.d(TAG, "++++++ resumeWith onFailure")
            context[CoroutineExceptionHandler]?.handleException(context, it)
        }
    }
})

首先,被 suspend 包裹的代码段就是协程需要去执行的协程体,最后还有一个返回值。其次,startCoroutine 方法中的匿名内部类 Continuation 实际上实现了协程上下文的配置以及协程执行完的回调。

在配置协程上下文中,使用了 CoroutineName 和 CoroutineExceptionHandler 添加了两个元素。CoroutineName 可以为协程绑定一个名字;CoroutineExceptionHandler 前文已有说明。

而 resumeWith 方法就是协程的回调方法,执行失败或完成都会回调,就拿上面的代码,在Activity onCreate 方法中执行,就会输出下面的信息:

image.png

可以看出,通过 CoroutineName 确实可以给协程绑定一个名字,而且在协程体中可通过 coroutineContext 协程上下文对象获取到协程上下文的一些信息;协程执行完成时,回调的是  resumeWith 中 Result 的 onSuccess 方法;协程执行出错时,回调的是  resumeWith 中 Result 的 onFailure  方法。如果把 code 3 中的 val tmp = 1/0去掉注释再运行,则会输出下面的情况:

image.png

协程上下文就说到这里。

目录
相关文章
|
20天前
|
调度 开发者 UED
Kotlin 中的协程是什么?
【8月更文挑战第31天】
37 0
|
3月前
|
存储 Java 调度
Android面试题之Kotlin 协程的挂起、执行和恢复过程
了解Kotlin协程的挂起、执行和恢复机制。挂起时,状态和上下文(局部变量、调用栈、调度器等)被保存;挂起点通过`Continuation`对象处理,释放线程控制权。当恢复条件满足,调度器重新分配线程,调用`resumeWith`恢复执行。关注公众号“AntDream”获取更多并发知识。
71 2
|
4月前
|
移动开发 Android开发 开发者
构建高效Android应用:Kotlin与协程的完美融合
【5月更文挑战第25天】 在移动开发的世界中,性能和响应性是衡量应用质量的关键指标。随着Kotlin的流行和协程的引入,Android开发者现在有了更强大的工具来提升应用的性能和用户体验。本文深入探讨了Kotlin语言如何与协程相结合,为Android应用开发带来异步处理能力的同时,保持代码的简洁性和可读性。我们将通过实际案例分析,展示如何在Android项目中实现协程,以及它们如何帮助开发者更有效地管理后台任务和用户界面的流畅交互。
|
4月前
|
移动开发 数据库 Android开发
构建高效Android应用:探究Kotlin的协程优势
【5月更文挑战第22天】随着移动开发技术的不断进步,Android平台的性能优化已经成为开发者关注的焦点。在众多提升应用性能的手段中,Kotlin语言提供的协程概念因其轻量级线程管理和异步编程能力而受到广泛关注。本文将深入探讨Kotlin协程在Android开发中的应用,以及它如何帮助开发者构建出更高效、响应更快的应用,同时保持代码的简洁性和可读性。
|
4月前
|
移动开发 Android开发 开发者
构建高效安卓应用:Kotlin 协程的实践指南
【5月更文挑战第18天】 随着移动开发技术的不断进步,安卓平台亟需一种高效的异步编程解决方案来应对日益复杂的应用需求。Kotlin 协程作为一种新兴的轻量级线程管理机制,以其简洁的语法和强大的功能,成为解决这一问题的关键。本文将深入探讨Kotlin协程在安卓开发中的实际应用,从基本概念到高级技巧,为开发者提供一份全面的实践指南,旨在帮助读者构建更加高效、稳定的安卓应用。
|
4月前
|
架构师 网络协议 算法
Android高级架构师整理面试经历发现?(大厂面经+学习笔记(1)
Android高级架构师整理面试经历发现?(大厂面经+学习笔记(1)
|
4月前
|
移动开发 安全 Android开发
构建高效Android应用:Kotlin与协程的完美结合
【5月更文挑战第17天】 在移动开发领域,性能优化和流畅的用户体验是关键。对于Android平台而言,Kotlin语言凭借其简洁性和功能安全性成为开发的首选。与此同时,协程作为一种新的并发处理方式,在简化异步编程方面展现出巨大潜力。本文将深入探讨如何通过Kotlin语言以及协程技术,提升Android应用的性能和响应能力,并确保用户界面的流畅性。
|
4月前
|
移动开发 数据处理 Android开发
构建高效Android应用:Kotlin的协程与Flow的使用
【5月更文挑战第23天】 在移动开发领域,性能优化和异步编程一直是核心议题。随着Kotlin语言在Android开发中的普及,其提供的协程(coroutines)和流式编程(Flow)功能为开发者带来了革命性的工具,以更简洁、高效的方式处理异步任务和数据流。本文将深入探讨Kotlin协程和Flow在Android应用中的实际应用,以及它们如何帮助开发者编写更加响应迅速且不阻塞用户界面的应用程序。我们将通过具体案例分析这两种技术的优势,并展示如何在现有项目中实现这些功能。
|
8天前
|
Android开发 开发者 Kotlin
告别AsyncTask:一招教你用Kotlin协程重构Android应用,流畅度飙升的秘密武器
【9月更文挑战第13天】随着Android应用复杂度的增加,有效管理异步任务成为关键。Kotlin协程提供了一种优雅的并发操作处理方式,使异步编程更简单直观。本文通过具体示例介绍如何使用Kotlin协程优化Android应用性能,包括网络数据加载和UI更新。首先需在`build.gradle`中添加coroutines依赖。接着,通过定义挂起函数执行网络请求,并在`ViewModel`中使用`viewModelScope`启动协程,结合`Dispatchers.Main`更新UI,避免内存泄漏。使用协程不仅简化代码,还提升了程序健壮性。
21 1