• Android Kotlin知识汇总(三)Kotlin 协程


    Kotlin的重要优势及特点之——结构化并发

    Kotlin 协程让异步代码像阻塞代码一样易于使用。协程可大幅简化后台任务管理,例如网络调用、本地数据访问等任务的管理。本主题介绍如何使用 Kotlin 协程解决以下问题,从而让您能够编写出更清晰、更简洁的应用代码。

    所有源文件都必须编码为 UTF-8。

    来源标注:Android 上的 Kotlin 协程  |  Android Developers

    书接上篇:Android Kotlin知识汇总(二)最佳实践-CSDN博客


    Android 上的 Kotlin 协程

    协程是一种并发设计模式,可以在 Android 平台上使用它来简化异步执行的代码。

    在 Android 上,协程有助于管理长时间运行的任务。使用协程的专业开发者中有超过 50% 的人反映使用协程提高了工作效率。

           简单来说,协程就是一种轻量级的非阻塞的线程工具API,可以用同步的方式写出异步的代码,优雅地切换线程和处理回调地狱。与线程的关系,线程在进程中,协程在线程中。


    协程特点

    协程是我们在 Android 上进行异步编程的推荐解决方案。值得关注的特点包括:

    • 轻量:可以在单个线程上运行多个协程,因为协程支持挂起,不会使正在运行协程的线程阻塞。挂起比阻塞节省内存,且支持多个并行操作。
    • 内存泄漏更少:使用结构化并发机制在一个作用域内执行多项操作。
    • 内置取消支持取消操作会自动在运行中的协程层次结构内传播。
    • Jetpack 集成:许多 Jetpack 库都包含提供全面协程支持的扩展。某些库还提供自己的协程作用域,可供您用于结构化并发。

    主流创建协程的方式

    使用协程时在代码写法上和普通的顺序代码类似。创建协程可以使用以下三种方式:

    1. // 方法1:使用 runBlocking 顶层函数
    2. runBlocking {}
    3. // 方法2:使用 GlobalScope 单例对象,调用 launch 开启协程
    4. GlobalScope.launch {}
    5. // 方法3:创建 CoroutineScope 对象,调用 launch 开启协程
    6. val coroutineScope = CoroutineScope(context)
    7. coroutineScope.launch {}
    • 方法 1: 适用于单元测试场景,实际开发中不推荐,因为它是线程阻塞的;
    • 方法 2: 不会阻塞线程,但它的生命周期会和 APP 一致,且无法取消;
    • 方法 3: 推荐使用,可以通过 context 参数去管理和控制协程的生命周期。

    使用协程确保主线程安全 

    通过launch()创建一个新的协程空间,{}内的代码块被叫做一个子协程。而传给 launch()的参数则用于指定执行这段代码运行的线程。

    1. coroutineScope.launch(Dispatchers.IO) {//参数切到IO线程执行
    2. }
    3. coroutineScope.launch(Dispatchers.Main) {//参数切到主线程执行
    4. }

    在 Kotlin 中,所有协程都必须在调度程序中运行,即使它们在主线程上运行也是如此。

    协程可以自行挂起,而调度程序负责将其恢复。 

    Kotlin 提供了三个调度程序,以用于指定应在何处运行协程:

    • Dispatchers.Main - 使用此调度程序可在 Android 主线程上运行协程。此调度程序只能用于与界面交互和执行快速工作。
    • Dispatchers.IO - 此调度程序经过了专门优化,适合在主线程之外执行磁盘或网络 I/O。
    • Dispatchers.Default - 此调度程序经过了专门优化,适合在主线程之外执行占用大量 CPU 资源的工作。用例示例包括对列表排序和解析 JSON。

    使用 withContext 方法

            该方法支持自动切回原来的线程,能够消除并发代码在协作时产生的嵌套。如果需要频繁地进行线程切换,这种写法将有很大的优势,即“使用同步的方式写异步代码”。如下所示:

    1. coroutineScope.launch(Dispatchers.Main) {// Dispatchers.Main
    2. val image = withContext(Dispatchers.IO) {// 切换到 IO 线程
    3. getImage(imageUrl) // 切换到 IO 线程
    4. }
    5. imageView.setImageBitmap(image) // Dispatchers.Main
    6. }

    withContext(Dispatchers.IO) 创建一个在 IO 线程池中运行的代码块。放在该块内的任何代码都始终通过 IO 调度程序执行。由于 withContext 本身就是一个挂起函数,因此函数 getImage() 也是一个挂起函数。  

    使用 suspend 关键字

    协程在常规函数的基础上添加了两项操作,用于处理长时间运行的任务。 

    • suspend 用于暂停执行当前协程,并保存所有局部变量。
    • resume 用于让已挂起的协程从挂起处继续执行。

    代码在执行到某个 suspend 函数时会从正在执行它的线程上脱离,协程会从被挂起的 suspend 函数指定的线程(如 Dispatchers.IO)中开始执行。当该 suspend方法执行完成之后,会重新切换回它原先的线程。这个「切回来」的动作,在 Kotlin 中叫做 resume。

    在上述示例中,把 withContext 单独放进一个getImage()里,并使用 suspend 关键字标记才能编译通过,示例代码如下: 

    1. suspend fun getImage(imageUrl: String) = // Dispatchers.Main
    2. withContext(Dispatchers.IO) { // Dispatchers.IO (main-safety block)
    3. /* network IO here */
    4. } // Dispatchers.Main

    如果调用 suspend 函数,只能从其他 suspend 函数进行调用时,代码如下所示:

    1. suspend fun fetchDocs() { // Dispatchers.Main
    2.     val result = getImage("url") // Dispatchers.IO
    3. }
    4. suspend fun getImage(url: String) = withContext(Dispatchers.IO) { /* ... */ }

    在上面的示例中,getImage() 仍在主线程上运行,但它会在启动网络请求之前挂起协程。当网络请求完成时,getImage() 会恢复已挂起的协程fetchDocs(),而不是使用回调通知主线程。 

    获取协程的返回值

    启动协程的方式有两种:

    • launch 启动新协程而不将结果返回给调用方。
    • async  在另一个协程内或在挂起函数内且在执行“并行分解”时才使用,并可以使用一个名为 await 的挂起函数返回结果。

    警告launch 和 async 处理异常的方式不同。由于 async 对 await 进行最终调用,因此它持有异常并将其作为 await 调用的一部分抛出。所以,使用 async 会有把异常静默吞掉的风险。

    并行分解 

    基于Kotlin 的结构化并发机制,您可以定义启动一个或多个协程的 coroutineScope。然后,您可以使用 await()或 awaitAll()保证这些协程在从函数返回结果之前完成。

    例如,在一个coroutineScope里执行两个并行的协程,此时通过调用 await()对每个延迟引用,就可以保证这两项 async 操作在返回值之前完成,代码如下所示:

    1. suspend fun fetchTwoDocs() =
    2. coroutineScope {
    3. val deferredOne = async { fetchDoc(1) }
    4. val deferredTwo = async { fetchDoc(2) }
    5. deferredOne.await()
    6. deferredTwo.await()
    7. }

    此外,coroutineScope 会捕获协程抛出的所有异常,并将其传送回调用方。

    协程的非阻塞式挂起

          「非阻塞式挂起」指的就是协程在挂起的同时切线程这件事情。使用了协程的代码看似阻塞,但由于协程内部做了很多工作(包括自动切换线程),它实际上是非阻塞的。        

            在代码执行的过程中,当线程执行到了 suspend 方法,就暂时不再执行剩余协程代码,跳出协程的代码块。如果它是一个后台线程,它会被系统回收或者再利用(继续执行别的后台任务),与 Java 线程池中的线程等同;如果它是 Android 主线程,它会继续执行界面刷新任务。


    代码示例 

            使用协程模拟实现一个网络请求,等待时显示 Loading,请求成功或者出错让 Loading 消失,并将状态反馈给用户。

    依赖项信息

    如需在 Android 项目中使用协程,请将以下依赖项添加到应用的 build.gradle 文件中:

    1. dependencies {
    2. implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9")
    3. }

    ViewModel 代码

    1. @HiltViewModel
    2. class MainViewModel @Inject constructor() : ViewModel() {
    3. enum class RequestStatus {
    4. IDLE, LOADING, SUCCESS, FAIL
    5. }
    6. val requestStatus = MutableStateFlow(RequestStatus.IDLE)
    7. /**
    8. * 模拟网络请求
    9. */
    10. fun simulateNetworkRequest() {
    11. requestStatus.value = RequestStatus.LOADING
    12. viewModelScope.launch {
    13. val requestResult = async { performSimulatedRequest() }.await()
    14. requestStatus.value =
    15. if (requestResult) RequestStatus.SUCCESS else RequestStatus.FAIL
    16. }
    17. }
    18. /**
    19. * 模拟耗时操作,随机数->成功或失败
    20. */
    21. private suspend fun performSimulatedRequest() =
    22. withContext(Dispatchers.IO) {
    23. delay(500)
    24. val random = Random()
    25. return @withContext random.nextBoolean()
    26. }
    27. }

    MainActivity 代码

    使用 Jetpack Compose,将请求状态实时显示在界面上。代码如下所示:

    1. @AndroidEntryPoint
    2. class MainActivity : ComponentActivity() {
    3. //声明model属性
    4. private val mainViewModel: MainViewModel by viewModels()
    5. override fun onCreate(savedInstanceState: Bundle?) {
    6. super.onCreate(savedInstanceState)
    7. setContent {
    8. ComposeTheme {
    9. Surface(modifier = Modifier.fillMaxSize(),color = MaterialTheme.colorScheme.background) {
    10. val requestStatusState =mainViewModel.requestStatus.collectAsState()
    11. val requestStatus by rememberSaveable {requestStatusState }
    12. Text(text = requestStatus.name,)
    13. }
    14. }
    15. }
    16. //请求网络
    17. mainViewModel.simulateNetworkRequest()
    18. }
    19. }

    小结

            在 Kotlin 中,协程就是基于线程来实现的一种更上层的工具 API,在设计思想上,协程是一个基于线程的上层框架。Kotlin 协程并没有脱离 Kotlin 或者 JVM 创造新的东西,只是简化了多线程的开发。

    下一篇,继续介绍 Kotlin 协程在 Jetpack 实战开发过程中最有用的一些方面。

  • 相关阅读:
    国产大模型新标杆!比肩GPT4,DeepSeek V2重磅升级
    如何制作我的第一个Phaser.js游戏
    前端面试真题宝典(二)
    黑马mysql教程笔记(mysql8教程)基础篇——数据库相关概念、mysql安装及卸载、数据模型、SQL通用语法及分类(DDL、DML、DQL、DCL)
    java中的interface(接口)
    Siri怎么打开
    【STL编程】【竞赛常用】【part 2】
    读书笔记:软件工程(3) - 软件生存周期
    springboot项目部署到linux服务器
    Github 2024-04-22 开源项目日报Top10
  • 原文地址:https://blog.csdn.net/csdn_aiyang/article/details/136633084