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

news2025/1/11 19:45:29

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

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

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

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

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


Android 上的 Kotlin 协程

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

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

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


协程特点

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

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

主流创建协程的方式

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

// 方法1:使用 runBlocking 顶层函数
runBlocking {}

// 方法2:使用 GlobalScope 单例对象,调用 launch 开启协程
GlobalScope.launch {}

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

使用协程确保主线程安全 

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

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

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

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

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

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

使用 withContext 方法

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

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

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

使用 suspend 关键字

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

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

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

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

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

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

suspend fun fetchDocs() { // Dispatchers.Main
    val result = getImage("url") // Dispatchers.IO 
}
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 操作在返回值之前完成,代码如下所示:

suspend fun fetchTwoDocs() =
    coroutineScope {
        val deferredOne = async { fetchDoc(1) }
        val deferredTwo = async { fetchDoc(2) }
        deferredOne.await()
        deferredTwo.await()
    }

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

协程的非阻塞式挂起

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

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


代码示例 

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

依赖项信息

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

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

ViewModel 代码

@HiltViewModel
class MainViewModel @Inject constructor() : ViewModel() {
    enum class RequestStatus {
        IDLE, LOADING, SUCCESS, FAIL
    }
    val requestStatus = MutableStateFlow(RequestStatus.IDLE)
    /**
     * 模拟网络请求
     */
    fun simulateNetworkRequest() {
        requestStatus.value = RequestStatus.LOADING
        viewModelScope.launch {
            val requestResult = async { performSimulatedRequest() }.await()
            requestStatus.value = 
                if (requestResult) RequestStatus.SUCCESS else RequestStatus.FAIL
        }
    }
    /**
     * 模拟耗时操作,随机数->成功或失败
     */
    private suspend fun performSimulatedRequest() = 
        withContext(Dispatchers.IO) {
            delay(500)
            val random = Random()
            return @withContext random.nextBoolean()
        }
    }

MainActivity 代码

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

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    //声明model属性
    private val mainViewModel: MainViewModel by viewModels()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeTheme {
                Surface(modifier = Modifier.fillMaxSize(),color = MaterialTheme.colorScheme.background) {
                   val requestStatusState =mainViewModel.requestStatus.collectAsState()
                   val requestStatus by rememberSaveable {requestStatusState }
                   Text(text = requestStatus.name,)
                }
            }
        }
        //请求网络
        mainViewModel.simulateNetworkRequest()
    }
}

小结

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1521256.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AI毕业论文降重GPTS,避免AI检测,高效完成论文

视频演示 AI毕业论文降重GPTS,避免AI检测,高效完成论文! 开发目的 “毕业论文降重”GPTS应用,作用为:重新表述学术论文,降低相似性评分,避免AI检测。 使用地址 地址:毕业论文降重…

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:ColumnSplit)

将子组件纵向布局,并在每个子组件之间插入一根横向的分割线。 说明: 该组件从API Version 7开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。 子组件 可以包含子组件。 ColumnSplit通过分割线限制子组件的高度。初始…

跨境电商选品实战——Ownips公开数据信息安全采集+Python爬虫轻松搞定Lazada电商选品

文章目录 一、引言二、Lazada电商平台选品实战2.1、分析Lazada电商平台的商品列表接口2.2、定位商品列表计算逻辑2.3、封装高质量住宅IP2.4、运行爬虫 三、数据处理及选品分析四、Ownips——企业级全球静态住宅IP,高效采集公开数据 一、引言 互联网与外贸的结合&am…

考察c语言关键字

C语言——关键字 1.问题:简述goto语句的作用 答:无条件跳转 具体来说,其作用在于允许程序在执行时无条件地跳转到指定的标签位置,并从该标签位置继续执行。通过goto语句,可以实现程序流程的无条件转移,使得…

【python】爬取杭州市二手房销售数据做数据分析【附源码】

一、背景 在数据分析和市场调研中,获取房地产数据是至关重要的一环。本文介绍了如何利用 Python 中的 requests、lxml 库以及 pandas 库,结合 XPath 解析网页信息,实现对链家网二手房销售数据的爬取,并将数据导出为 Excel 文件的过…

Visual Studio项目模板的创建与使用

Visual Studio项目模板的创建、使用、删除 创建模板项目模板的使用模板的删除 创建模板 点击项目,点击导出模板 选择你要创建哪个项目的项目模板,点击下一步 输入你的模板名称并添加模板说明,方便记忆 项目模板的使用 点击创建新项目 输入刚刚…

什么是VR应急预案演练虚拟化|VR体验馆加盟|元宇宙文旅

VR 应急预案演练虚拟化指的是利用虚拟现实(Virtual Reality,VR)技术进行应急预案演练的过程。在传统的应急预案演练中,人们通常需要在实际场地或模拟环境中进行演练,这可能存在一些限制,如成本高昂、场地受…

Cesium 获取 3dtileset的包围盒各顶点坐标

Cesium 获取 3dtileset的包围盒各顶点坐标 /*** 获取 3dtileset的包围盒各顶点坐标, z 方向取高度最低的位置* param {*} tileset* param {*} options* returns* ref https://blog.csdn.net/STANDBYF/article/details/135012273* ref https://community.cesium.com/t/accurate-…

PTA L2-014 列车调度

火车站的列车调度铁轨的结构如下图所示。 两端分别是一条入口(Entrance)轨道和一条出口(Exit)轨道,它们之间有N条平行的轨道。每趟列车从入口可以选择任意一条轨道进入,最后从出口离开。在图中有9趟列车&am…

uniapp 对video视频组件嵌套倍速按钮

这次接了需求是要求有倍速功能,去看了文档发现并没有倍速按钮的属性,想着手写一个吧 可最后发现原生层级太高,无论怎么样都迭不上去,就只能去找插件看看咯 找了好多插件发现都不可用,因为我这是app端,有些视…

【蓝桥杯单片机】十三届省赛“重难点”解析(附源码)

【蓝桥杯单片机】十三届省赛“重难点”解析 一、题目难点解析二、易出错点提示三、完整代码链接 笔记包括:①题目难点解析、②易出错点提示、③完整代码链接 注:本文提供的所有代码都是使用第十四届竞赛包完成 ⭐----------系列文章链接----------⭐ 【蓝…

2024年AI辅助研发:科技创新的引擎

CSND - 个人主页:17_Kevin-CSDN博客 收录专栏:《人工智能》 技术进展 进入2024年,人工智能(AI)在科技界和工业界的焦点地位更加巩固,其在辅助研发领域的技术进步尤为显著。深度学习技术的突飞猛进使得数据分…

<.Net>VisaulStudio2022下用VB.net实现socket与汇川PLC进行通讯案例(Eazy521)

前言 此前,我写过一个VB.net环境下与西门子PLC通讯案例的博文: VisaulStudio2022下用VB.net实现socket与西门子PLC进行通讯案例(优化版) 最近项目上会用到汇川PLC比较多,正好有个项目有上位机通讯需求,于是…

三.查找(顺序/二分)

目录 7-顺序查找(列表查找) 1-什么是列表查找 代码: 8-二分查找介绍(Binary Search) 查找元素3详细思路: 9-二分查找代码 10-线性查找与二分查找比较 运行时间-装饰器 比较代码: 11-排序介绍 7-顺序查找(列表查找) 1-什么是列表查找 查找:在一些数据元素…

MAC M芯片 Anaconda安装

Anaconda安装 1.M芯片下载AnaConda 1.M芯片下载AnaConda https://www.anaconda.com/download 安装完成 conda的版本是24.1.2

预备知识:深入理解接口测试!

实验简介 随着移动互联网甚至物联网的触角深入到人们生活的每个场景,每个角落,伴随而来的便是企业对其软件系统接口定义和研发,以便于进行数据传输和交换。由此导致目前企业急需大量专职接口测试工程师,因为接口测试天然具备自动…

机试:偶数分解

题目描述: 代码示例: #include <bits/stdc.h> using namespace std; int main(){ // 算法思想1:遍历小于该偶数的所有素数,存入数组中,遍历数组找出两个数之和等于偶数的数int n;cout << "输入样例" << endl;cin >> n;int nums[n];int k …

Python内存管理与垃圾回收机制:深入理解与优化【第138篇—RESTful API】

Python内存管理与垃圾回收机制&#xff1a;深入理解与优化 在Python编程中&#xff0c;内存管理与垃圾回收机制是至关重要的主题。了解Python如何管理内存和处理垃圾回收对于编写高效、稳定的程序至关重要。本文将深入探讨Python中的内存管理和垃圾回收机制&#xff0c;包括内…

CSS 【详解】响应式布局(明天内容)

响应式布局&#xff1a; 同一页面在不同的屏幕上有不同的布局&#xff0c;即一套代码自适应不同的屏幕。 常用 单位&#xff1a; 像素&#xff08;px&#xff09;&#xff1a;像素是最常用的长度单位&#xff0c;它表示屏幕上的一个物理像素点。例如&#xff0c;width: 200px; …

Bean的作用域、Bean的自动装配、注解自动装配 (Spring学习笔记五)

1、Bean 的作用域 官网上显示有六种 1、Bean的作用域默认的是singleton&#xff08;单例模式的实现&#xff09; 也可以显示的设置&#xff08;单例模式的实现&#xff09; <!--用scope可以设置Bean的作用域--><bean id"user2" class"com.li.pojo.Us…