五分钟技术趣谈 | 试论Android异步框架Kotlin协程
创始人
2025-06-30 12:30:58
0

Part 01

 什么是协程 

作为开发人员尤其是客户端应用开发,我们一直面临着需要解决的问题——如何防止我们的应用程序被阻塞。考虑下面一个异步应用场景。客户端顺序进行3次网络请求,最后更新UI展示结果。

图片图片

图1 异步场景

有多种方法实现上述需求,主流的包括:

  • 回调
  • Rx(反应式扩展)
  • 协程

1.1 回调方式

图片

图2 回调代码示例

异步回调的方式虽然实现了需求,但是这种结构的代码无论是阅读还是维护起来都是极其糟糕的。这种回调函数的层层嵌套耦合,亲切地称为 "回调地狱"。

1.2 Rx方式

图片

图3 Rx代码示例

Rx系列的链式调用,是在协程之前推荐的做法,RxJava丰富的操作符、简便的线程调度、异常处理使得大多数人满意。但是还有没有更简洁易读的写法呢?

1.3 协程方式

图片

图4 协程代码示例

使用协程后的代码非常简洁,以顺序的方式编写异步代码,不会阻塞当前UI线程,错误处理、线程切换也和平常代码一样简单。

协程具有以下几个特点:

  • 轻量:您可以在单个线程上运行多个协程,因为协程支持挂起,挂起时不需要阻塞线程,几乎是无代价的,协程是由开发者控制的。所以协程也像用户态的线程,非常轻量级。
  • 内存泄漏更少:使用结构化并发机制在一个作用域内执行多项操作。
  • 内置取消支持:取消操作会自动在运行中的整个协程层次结构内传播。
  • Jetpack集成:许多Jetpack库都提供全面协程支持的扩展。某些库还提供自己的协程作用域,可用于结构化并发。

总而言之:协程可以简化异步编程,可以顺序地表达程序。协程使用挂起,这意味可以在代码的特定点暂停和恢复执行,无需阻塞主线程或显示创建额外的线程。

Part 02

协程的使用 

- 引入gradle依赖

图片

图5 gradle依赖引入

- 启动协程

图片

图6 启动协程

上面就是启动协程的代码,启动协程的代码可以分为三部分:GlobalScope、launch、Dispatchers,它们分别对应:协程的作用域、构建器和调度器。

2.1 协程作用域

指的是协程内的代码运行的时间周期范围,如果超出了指定的协程范围,协程会被取消执行。

官方库给我们提供了一些作用域可以直接来使用:

  • runBlocking

顶层函数,但是它会阻塞当前线程,主要用于测试。

  • GlobalScope

全局协程作用域,它启动的协程的生命周期只受整个应用程序的生命周期的限制,且不能取消,运行时会消耗一些内存资源,这可能会导致内存泄露,不适用于业务开发。

  • coroutineScope

创建一个独立的协程作用域,直到所有启动的协程都完成后才结束自身。它是一个挂起函数,需要运行在协程内或挂起函数内,为并行分解工作而设计的。

  • supervisorScope

与coroutineScope类似,不同的是子协程的异常不会影响父协程,也不会影响其他子协程。

  • MainScope

为UI组件创建主作用域。一个顶层函数,上下文是SupervisorJob() + Dispatchers.Main,说明它是一个在主线程执行的协程作用域。推荐使用。

Android官方对协程的支持是非常友好的,KTX为Jetpack的Lifecycle相关组件提供了已经绑定UV声明周期的作用域供我们直接使用:

  • lifecycleScope

与Lifecycle绑定生命周期,生命周期被销毁时,此作用域将被取消不会造成协程泄漏,推荐使用。

  • viewModelScope

与lifecycleScope类似,与ViewModel绑定生命周期,当ViewModel被清除时,这个作用域将被取消,推荐使用。

2.2 调度器

调度器的作用是将协程限制在特定的线程执行。主要的调度器类型有:

  • Dispatchers.Main:指定执行的线程是主线程
  • Dispatchers.IO:指定执行的线程是IO线程
  • Dispatchers.Default:默认的调度器,适合执行CPU密集性的任务
  • Dispatchers.Unconfined:非限制的调度器,指定的线程可能会随着挂起的函数的发生变化

2.3 构建器

kotlinx.continues库提供的三个基本协程构建器:

  • Launch
  • async
  • runBlocking

launch{}是最常用的协程构建器,不阻塞当前线程,在后台创建一个新协程,也可以指定协程调度器。

async创建一个新的协程,不会阻塞当前线程,必须在协程作用域中才可以调用,并返回Deffer对象。可通过调用Deffer.await()方法等待该子协程执行完成并获取结果。常用于并发执行-同步等待和获取返回值的情况。

runBlocking是创建一个新的协程同时阻塞当前线程,直到协程结束,主要是为测试设计。

Part 03

协程挂起、恢复原理剖析 

协程的概念最核心的点就是挂起,即函数或者某段程序可以在某个时刻暂停执行并稍后恢复。suspend是Kotlin协程最核心的关键字,使用suspend关键字修饰的函数叫作挂起函数,挂起函数只能在协程体内或者在其他挂起函数内调用。内部实现使用了Kotlin编译器的一些编译技术,被关键字suspend修饰的方法在编译阶段,编译器会修改方法的签名. 包括返回值,修饰符,入参,方法体实现。我们以下面一个简单的挂起方法来剖析。

图片

图7 挂起函数

通过AS的工具栏中 Tools->Kotlin->show Kotlin ByteCode,得到java字节码,再点击Decompile按钮反编译成java源码:

图片

图8 挂起函数反编译java源码

上面主要步骤为:

1️⃣函数返回值变成Object,函数入参编译后增加了Continuation参数。

2️⃣创建一个ContinuationImpl ,复写invokeSuspend()方法,在这个方法里面它又调用了一次自己,并且把continuation传递进去。

3️⃣在switch状态机中,label初始值为0,第一次会进入case 0分支,delay()是一个挂起函数,传入上面的continuation参数,会有一个Object类型的返回值。

4️⃣DelayKt.delay(2000, continuation)的返回结果如果是 COROUTINE_SUSPENDED,则直接return,那么方法执行就被结束了,方法就被挂起了。

这就是挂起的真正原理。协程的挂起本质上是方法的挂起,而方法的挂起本质上是return,协程的恢复本质上方法的恢复,而恢复的本质是callback回调。

Part 04

 总结 

异步编程是现代软件开发的重要组成部分,它允许我们创建响应迅速、可扩展的应用程序。Kotlin协程是一款轻量级、高效、易于使用的并发框架,借助Kotlin的语言优势,用同步的方式写出异步的代码,变得更加可维护和可读,有助于改善开发体验。在Android客户端开发中,结合Jetpack可以更加轻松使用不阻塞UI线程同时避免内存泄露。

相关内容

热门资讯

如何允许远程连接到MySQL数... [[277004]]【51CTO.com快译】默认情况下,MySQL服务器仅侦听来自localhos...
如何利用交换机和端口设置来管理... 在网络管理中,总是有些人让管理员头疼。下面我们就将介绍一下一个网管员利用交换机以及端口设置等来进行D...
施耐德电气数据中心整体解决方案... 近日,全球能效管理专家施耐德电气正式启动大型体验活动“能效中国行——2012卡车巡展”,作为该活动的...
20个非常棒的扁平设计免费资源 Apple设备的平面图标PSD免费平板UI 平板UI套件24平图标Freen平板UI套件PSD径向平...
德国电信门户网站可实时显示全球... 德国电信周三推出一个门户网站,直观地实时提供其安装在全球各地的传感器网络检测到的网络攻击状况。该网站...
为啥国人偏爱 Mybatis,... 关于 SQL 和 ORM 的争论,永远都不会终止,我也一直在思考这个问题。昨天又跟群里的小伙伴进行...
《非诚勿扰》红人闫凤娇被曝厕所... 【51CTO.com 综合消息360安全专家提醒说,“闫凤娇”、“非诚勿扰”已经被黑客盯上成为了“木...
2012年第四季度互联网状况报... [[71653]]  北京时间4月25日消息,据国外媒体报道,全球知名的云平台公司Akamai Te...