协程模式在Android中的应用及工作原理

协程模式在Android中的应用及工作原理

在Android开发中,很多开发者通过代码模式学习协程,通常这已经足够应付了。但这种学习方式忽略了协程背后的精髓,事实上,它们的原理非常简单。那么,是什么使得这些模式起作用呢?

拿起你的工具,我们来揭开一些常见的协程模式,这些模式你可能已经见过很多次,并惊叹于它们背后的奥妙。

当然,如果你对协程还不太熟悉,那么欢迎!以下是一些对Android开发者来说非常值得学习的模式。

模式1:挂起函数

就像做吐司一样简单。你可能已经知道了:

  1. 把面包放进烤箱里。
  2. 等一会儿。
  3. 从烤箱里拿出烤好的面包。

下面是用Kotlin写的这个过程:

suspend fun makeToast() {println("把面包放进烤箱")delay(2000)println("面包现在是烤好的了")
}

如果你回顾一下整个过程,你会发现你大部分时间都是在等待面包变成烤面包。只有很少的时间你真正在活动。

那么在等待的时候你可以做些什么呢?嗯,任何你喜欢的事情。你可以在待办事项上勾掉另一项任务。只要你及时回来处理烤好的面包,就没问题。

这就是挂起函数的作用。在等待的过程中,协程被挂起,这告诉协程库(具体来说是调度器)它可以做其他的事情。

所以,这是关键部分——当你调用这个挂起函数时,底层线程并没有被阻塞。协程库高效地利用了等待的时间,让线程继续工作。

当然,对于调用上面的makeToast()函数的代码来说,这些细节并不重要。你调用makeToast(),函数稍后返回,一旦面包烤好,它就会通知你。无论它是坐着等面包,还是做其他工作,都不影响你的调用。

模式2:从主线程调用挂起函数

这就是为什么通常可以安全地从主/UI线程调用挂起函数的原因。因为挂起函数不会阻塞主线程,所以主线程可以继续进行UI操作。

下面是一个示例。点击按钮后,我们会显示一个PIN码10秒钟,然后再隐藏它:

//MainActivity.kt
@Composable
fun PlanetsScreen(...) {val revealPIN by viewModel.isShowingPin.collectAsStateWithLifecycle()val scope = rememberCoroutineScope()Column {Button(onClick = {scope.launch {// Here we call a function which takes at least 10 seconds to run,// directly from the main thread. Safe because the thread isn't blocked.viewModel.revealPinBriefly()}}) {Text("Reveal PIN")}if (revealPIN) {Text(text = "Your PIN is 1234")}}
}
//MyViewModel.kt
val isShowingPin = MutableStateFlow(false)// This function suspends the coroutine for a long time, but
// doesn't block the calling thread. So it can be called from
// the main/UI thread safely.
suspend fun revealPinBriefly() {isShowingPin.value = truedelay(10_000)isShowingPin.value = false
}

这是完全安全的,因为它不会阻塞用户界面线程。在这10秒的延迟期间,用户界面仍然可以响应。

模式3:切换上下文

许多挂起函数大部分时间都是处于挂起状态。一个很好的例子是从互联网获取数据:建立连接很容易,但等待数据下载占据了大部分时间。

那么,在用户界面线程上执行挂起的网络任务是否安全?不!根本不安全。

调用线程只在挂起任务实际被挂起的时间内(即等待期间)解除阻塞。

网络任务涉及各种等待之外的工作:设置连接、加密、解析响应等。它们可能只需要几毫秒的时间,但这是用户界面线程被阻塞的几毫秒。

出于性能原因,你需要确保用户界面线程持续更新界面。不要中断它,否则你的应用程序性能会受到影响。

因此,我们有了“切换上下文”的模式:

//NotesRepository.kt
suspend fun saveNote(note: Note) {withContext(Dispatchers.IO) {notesRemoteDataSource.saveNote(note)}
}

上面的withContext确保该挂起函数在IO线程池上运行。有了这个设置,可以安全地从用户界面线程调用saveNote函数。

作为一个通用规则:确保挂起函数在需要时切换上下文,以便可以从用户界面线程调用它们。

模式4:在作用域中运行协程

这不是一个具体的模式,因为所有的协程都需要在某个上下文中运行。

但以下面的例子为例,像这样的代码实际上是什么意思?

viewModelScope.launch {// Do something
}

让我们从简单的角度来看:协程的作用域表示它的生命周期。实际上还有更多细节,我会在以后的文章中详细介绍,但这是一个很好的起点。

所以,通过使用viewModelScope.launch,你是在说:“启动一个协程,它的生命周期受到viewModelScope的限制”。

因此,这里的viewModelScope就像是一个容器,用来保存View Model的协程,包括上面的那个协程。当容器被清空时——也就是当viewModelScope被取消时,其中的内容也将被取消。以实际情况来说,这意味着你可以编写代码,而不必担心何时关闭它。

模式5:在挂起函数中执行多个操作

我们首先接触到了viewModelScope。还有许多其他的,例如:

在Compose中有rememberCoroutineScope(),它提供了一个作用域,持续时间与@Composable在屏幕上的时间相同。(上面的模式1有一个示例)
在Android视图中有viewLifecycleOwner.lifecycleScope,它持续时间与Activity/Fragment相同
GlobalScope永远持续(因此通常是一个不好的主意™,但并非总是如此)
或者,你可以像下面这个模式一样自己创建:

//NotesRepository.kt 
suspend fun deleteAllNotes() = withContext(...) {// Create a scope. The suspend function will return when *all* the// scope's child coroutines finish.coroutineScope {launch { remoteDataSource.deleteAllNotes() }launch { localDataSource.deleteAllNotes() }}
}

那么为什么你想这样做呢?well,coroutineScope是一个特殊的函数,它创建一个新的协程作用域,并挂起,直到它内部的所有子协程都完成。

所以上面的模式意味着“并行执行这些任务,当它们全部完成时再返回”。

例如,在具有本地和远程数据源的仓库类中,这非常有用,因为你经常希望同时对两个数据源执行某些操作。只有当两个操作都完成时,才认为该操作已完成。

模式6:无限循环

现在我们理解了协程作用域,我们可以看到为什么这种模式实际上是可行的:

//MyViewModel.kt
fun flashTheLights() {viewModelScope.launch {// This seems like an unsafe infinite loop, but in fact// it'll shut down when the viewModelScope is cancelled.while(true) {delay(1_000)lightState = !lightState}}
}

在5年前,while(true)这样的代码会被认为是一个巨大的问题,但在这种情况下实际上是安全的。一旦viewModelScope被取消,启动的协程也会被取消,这样这个“无限”循环就会停止。

但它停止的原因非常有趣…

调用delay()函数会让出线程给协程调度器。这意味着它允许协程调度器检查是否有其他任务需要执行,并且可以进行处理。

但同时,协程调度器也会检查协程是否已被取消,如果是的话,会抛出CancellationException异常。虽然你不需要对此异常进行处理,但结果是堆栈展开,while(true)这部分的代码会被丢弃。

反模式:一个不会挂起的挂起函数

因此,让出线程给协程调度器是非常重要的。你可以放心地使用Room、Retrofit和Coil等库,因为它们会在需要时将任务交给调度器处理。

但这也是为什么永远不应该编写这样的协程代码的原因:

//main.kt
// !!!!! DON'T DO THIS !!!!!
suspend fun countToAHundredBillion_unsafe() {var count = 0L// This suspend fun won't be cancelled if the coroutine// that's running it gets cancelled, because it doesn't// ever yield.while(count < 100_000_000_000) {count++}
}

这个程序需要很长时间才能运行完毕。而且一旦开始,就无法停止。

为了确保协程的安全性,可以使用yield()函数。yield()有点像运行delay()函数,但并不会真正延迟执行,它会让出给调度器,并在需要停止时接收到CancellationException异常。

下面是一个安全版本的函数:

//main.kt
suspend fun countToAHundredBillion() {var count = 0Lwhile(count < 100_000_000_000) {count++// Every 10,000 we yield to the coroutine// dispatcher, allowing this loop to be// cancelled if needed.if (count % 10_000 == 0) {yield()}}
}

所以,这里总共有六种使用协程的模式和一种反模式。最重要的是,我们了解了它们为什么有效以及背后的原理。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

克魔助手 - iOS性能检测平台

前言 众所周知&#xff0c;如今的用户变得越来越关心app的体验&#xff0c;开发者必须关注应用性能所带来的用户流失问题。目前危害较大的性能问题主要有&#xff1a;闪退、卡顿、发热、耗电快、网络劫持等&#xff0c;但是做过iOS开发的人都知道&#xff0c;在开发过程中我们…

vue3+echarts:Vue中使用echarts从后端获取数据并赋值显示

//由于前后端交互,所以使用axios发送请求 const Count ref(null); //设备种类数值 const Name ref(null); //设备种类名称 //设备种类 饼图 const pieChart () > {const getpieChart echarts.init(document.getElementById("deviceKind"));// 创建图标getpieC…

使用 Matlab 拟合函数

1 加载数据 主页—>新建变量 粘贴 X 坐标&#xff0c;重命名变量名 同样的步骤&#xff0c;新建变量&#xff0c;加入 y 值 2 多项式拟合 打开APP&#xff0c;在数学工具里面选择--------》Curve Fitting 3 加载数据&#xff0c;选择功能

k8s中cert-manager管理https证书

前言 目前https是刚需,但证书又很贵,虽然阿里云有免费的,但没有泛域名证书,每有一个子域名就要申请一个证书,有效期1年,1年一到全都的更换,太麻烦了。经过搜索,发现了自动更新证书神器cert-manager;当然cert-manager是基于k8s的。 安装采用Helm方式 Chart地址: ht…

蓝桥杯刷题day06——平均

1、题目描述 有一个长度为n 的数组&#xff08;n 是 10 的倍数&#xff09;&#xff0c;每个数ai都是区间 [0,9] 中的整数。 小明发现数组里每种数出现的次数不太平均&#xff0c;而更改第i 个数的代价为bi&#xff0c; 他想更改若干个数的值使得这10 种数出现的次数相等&…

ArcGIS学习(五)坐标系-2

3.不同基准面坐标系之间的转换 在上一关中,我们学习了ArcGIS中的投影(投影栅格)工具,并以"WGS1984地理坐标系与WGS1984的UTM投影坐标系的转换”为例进行讲解。 "WGS1984地理坐标系与WGS1984的UTM投影坐标系的转换”代表的是同一个基准面下的两个坐标的转换。 …

微服务-微服务Alibaba-Nacos 源码分析 (源码流程图)-2.0.1

客户端注册临时实例&#xff0c;GRPC处理 客户端服务发现 及订阅处理 客户端数据变换&#xff0c;数据推送&#xff0c;服务端集群服务数据同步

vulhub中Adminer ElasticSearch 和 ClickHouse 错误页面SSRF漏洞复现(CVE-2021-21311)

Adminer是一个PHP编写的开源数据库管理工具&#xff0c;支持MySQL、MariaDB、PostgreSQL、SQLite、MS SQL、Oracle、Elasticsearch、MongoDB等数据库。 在其4.0.0到4.7.9版本之间&#xff0c;连接 ElasticSearch 和 ClickHouse 数据库时存在一处服务端请求伪造漏洞&#xff08…

20240206三次握手四次挥手

TCP和UDP异同点 相同点&#xff1a;同属于传输层的协议 不同点&#xff1a; TCP ----> 稳定 1> 提供面向连接的&#xff0c;可靠的数据传输服务 2> 传输过程中&#xff0c;数据无误、数据无丢失、数据无失序、数据无重复 1、TCP会给每个数据包编上编号&#xff…

计算机网络-华为无线网络配置

前面已经大致了解了无线通信的原理和无线组网的概念&#xff0c;今天来学习无线的配置过程与步骤。 一、无线组网配置流程 在开始配置前复习下前面讲过无线组网有涉及几个设备&#xff0c;AC无线控制器、AP无线接入点、POE交换机。无线组网与有线组网是相对独立的&#xff0c;不…

Python tkinter (15) —— PhotoImage

本文主要介绍Python tkinter PhotoImage图像应用及示例。 系列文章 python tkinter窗口简单实现 Python tkinter (1) —— Label标签 Python tkinter (2) —— Button标签 Python tkinter (3) —— Entry标签 Python tkinter (4) —— Text控件 Python tkinter (5) 选项按…

计算机网络-流量控制(数据链路层的流量控制及与传输层流量控制的区别 流量控制的方法 可靠传输,滑动窗口,流量控制三者关系)

文章目录 数据链路层的流量控制及与传输层流量控制的区别流量控制的方法各方法对应的发生窗口和接收窗口大小 可靠传输&#xff0c;滑动窗口&#xff0c;流量控制三者关系小结 数据链路层的流量控制及与传输层流量控制的区别 端到端&#xff1a;两个主机之间的 点对点&#xf…

idea设置terminal为git

要在IntelliJ IDEA中设置终端为Git Bash&#xff0c;请按照以下步骤操作&#xff1a; 打开 Settings&#xff08;设置&#xff09;。点击 Tools&#xff08;工具&#xff09;选项卡。进入 Terminal&#xff08;终端&#xff09;界面。在 Shell Path 下选择 Browse&#xff08;…

51单片机基础:定时器

1.定时器介绍 51单片机通常有两个定时器&#xff1a;定时器 0/1&#xff0c;好一点的可能有定时器3。 在介绍定时器之前我们先科普下几个知识&#xff1a; 1&#xff0c;CPU 时序的有关知识 ①振荡周期&#xff1a;为单片机提供定时信号的振荡源的周期&#xff08;晶振周期或…

golang 引入swagger(iris、gin)

golang 引入swagger&#xff08;iris、gin&#xff09; 在开发过程中&#xff0c;我们不免需要调试我们的接口&#xff0c;但是有些接口测试工具无法根据我们的接口变化而动态变化。文档和代码是分离的。总是出现文档和代码不同步的情况。这个时候就可以在我们项目中引入swagge…

Linux的打包压缩与解压缩---tar、xz、zip、unzip

最近突然用到了许久不用的压缩解压缩命令&#xff0c;真的陌生&#xff0c; 哈哈&#xff0c;记录一下&#xff0c;后续就不用搜索了。 tar的打包 tar -cvf 压缩有的文件名称 需要压缩的文件或文件夹tar -cvf virtualbox.tar virtualbox/ tar -zcvf virtualbox.tar virtualbo…

NX/UG二次开发—其他—矩形套料(排料)简介

算法逻辑 排料方法一定时间内获取近似解的算法 看了一些论文和博客&#xff0c;一般排料方法采用最低水平线算法排料&#xff0c;再此基础上增加空余区域填充。 然后配合遗传学算法||模拟退火算法||蚁群算法||免疫算法等&#xff0c;在一定时间内求得一组最优解。 在最简单的…

React+Antd+tree实现树多选功能(选中项受控+支持模糊检索)

1、先上效果 树型控件&#xff0c;选中项形成一棵新的树&#xff0c;若父选中&#xff0c;子自动选中&#xff0c;子取消&#xff0c;父不取消&#xff0c;子选中&#xff0c;所有的父节点自动取消。同时支持模糊检索&#xff0c;会检索出所有包含该内容的关联节点。 2、环境准…

嵌入式学习Day14 C语言 --- 位运算

位运算 注意&#xff1a;符号位也遵循这个规则 一、按位与(&) 运算规则&#xff1a;一假则假 int a 0x33;a & 0x55;0011 00110101 0101 &----------0001 0001 //0x11 二、按位或(|) 运算规则&#xff1a;一真则真 int a 0x33;a |0x55;0011 00110101 0101 |…

使用Python语言生成区块链地址

# 单次运行 import binascii import sha3 from ecdsa import SigningKey, SECP256k1priv SigningKey.generate(curveSECP256k1) # 生成私钥 pub priv.get_verifying_key() # 生成公钥keccak sha3.keccak_256() keccak.update(pub.to_string()) # keccak_256哈希运算 addr…