协程模式在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.hqwc.cn/news/457794.html

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

相关文章

博客|基于Springboot的个人博客系统设计与实现(源码+数据库+文档)

个人博客系统目录 目录 基于Springboot的个人博客系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、管理员功能实现 &#xff08;1&#xff09;用户管理 &#xff08;2&#xff09;文章分类管理 &#xff08;3&#xff09;公告信息管理 &#xff08;4&#…

【Java从入门到精通】Java对象和类

Java 对象和类 Java作为一种面向对象语言。支持以下基本概念&#xff1a; 多态继承封装抽象类对象实例方法重载 本节我们重点研究对象和类的概念。 对象&#xff1a;对象是类的一个实例&#xff08;对象不是找个女朋友&#xff09;&#xff0c;有状态和行为。例如&#xff0c…

APIfox自动化编排场景(二)

测试流程控制条件 你可以在测试场景中新增流程控制条件&#xff08;循环、判断、等待、分组&#xff09;等。进一步满足了更复杂的测试场景/流程配置的使用&#xff0c;最终借助自动化测试功能解决复杂场景的测试工作。 分组​ 当测试流程中多个步骤存在相关联关系时&#xf…

模型蒸馏distill /模型剪枝 论文汇总

文章目录 引言1. 蒸馏1&#xff09;白盒蒸馏DistilBERTPatient Knowledge DistillationTinyBERT 2020MiniLLM 2&#xff09;黑盒蒸馏Stanford alpacaVicunaWizardlmInstruction tuning with gpt-4Minigpt-4 2. 剪枝16个注意力头比一个好吗Movement pruning 1&#xff09;结构化…

Spring是怎么解决循环依赖的

首先先解释一下什么叫循环依赖 循环依赖:循环依赖其实就是循环引用,也就是两个或两个以上的bean互相持有对方,最终形成闭环.比如A依赖于B,B依赖于A 循环依赖在spring中是允许存在的,spring框架依据三级缓存已经解决了大部分的循环依赖 一级缓存:单例池,缓存已经经历了完整的…

七月论文审稿GPT第2.5和第3版:分别微调GPT3.5、Llama2 13B以扩大对GPT4的优势

前言 自去年7月份我带队成立大模型项目团队以来&#xff0c;我司至今已有5个项目组&#xff0c;其中 第一个项目组的AIGC模特生成系统已经上线在七月官网第二项目组的论文审稿GPT则将在今年3 4月份对外上线发布第三项目组的RAG知识库问答第1版则在春节之前已就绪至于第四、第…

各款Excel、word在线预览工具对比分析以及onlyoffice预览Excel加载时间长的解决方案

对于onlyoffice插件预览慢的问题分析&#xff1a; 研究了一下onlyoffice&#xff0c;得出以下结论&#xff01; 对于预览慢的问题&#xff0c;原因出在文件类型上&#xff0c;文件类型为低版本xls而非新版xlsx文件&#xff0c;onlyoffice服务器会自动将该文件转换为xlsx文件再…

NLP中的嵌入和距离度量

本文将深入研究嵌入、矢量数据库和各种距离度量的概念&#xff0c;并提供示例和演示代码。 NLP中的嵌入 嵌入是连续向量空间中对象、单词或实体的数值表示。在NLP中&#xff0c;词嵌入捕获词之间的语义关系&#xff0c;使算法能够更好地理解文本的上下文和含义。 让我们试着用…

外包干了10个月,技术退步明显.......

先说一下自己的情况&#xff0c;大专生&#xff0c;18年通过校招进入武汉某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落! 而我已经在一个企业干了四年的功能测…

研发误删的库,凭什么要 DBA 承担责任

镇楼图 三个角色 删库以及更宽泛的数据库变更场景中有三个角色&#xff0c;业务研发&#xff0c;DBA 以及使用的数据库变更工具&#xff1a; 业务研发通常指的是后端研发。国内最主流的技术栈还是 Java&#xff0c;此外 Go 也有一部分&#xff0c;另有全栈的则使用 Node。这些…

QTabWidget和QTabBar控件样式设置(qss)

QTabWidget和QTabBar控件样式设置 1、QTabWidget样式可自定义的有哪些示例&#xff1a;效果图 2、QTabBar样式可自定义的有哪些示例效果图 1、QTabWidget样式可自定义的有哪些 QTabWidget::pane{} 定义tabWidgetFrameQTabWidget::tab-bar{} 定义TabBar的位置QTabWidget::tab{}定…

如何安装x11vnc并结合cpolar实现win远程桌面Deepin

文章目录 1. 安装x11vnc2. 本地远程连接测试3. Deepin安装Cpolar4. 配置公网远程地址5. 公网远程连接Deepin桌面6. 固定连接公网地址7. 固定公网地址连接测试 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的 人工智能学习网站&#xff0c; 通俗易懂&#xff…