Android Studio 中的协程一. 概述二. 实现比赛进度2.1. 实现run方法2.2. LaunchedEffect 方法2.3. 结构化并发2.4. 协程作用域2.5 rememberCoroutineScope三. 单元测试3.1. 导入单元测试库3.2. 编写测试代码一. 概述我们这里使用一个模拟选手赛跑的demo来学洗Android Studio中的协程。点按 Start 按钮开始比赛。比赛过程中Start 按钮上的文字将变为 Pause。您可以随时使用此按钮暂停或继续比赛。比赛开始后您可以通过状态指示器查看每位选手的进度。StatusIndicator 可组合函数会显示每位选手的进度状态。该函数使用 LinearProgressIndicator 可组合项来显示进度条。您将使用协程来更新进度值。二. 实现比赛进度2.1. 实现run方法/* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the License); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an AS IS BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */packagecom.example.racetracker.uiimportandroid.util.Logimportandroidx.compose.runtime.getValueimportandroidx.compose.runtime.mutableStateOfimportandroidx.compose.runtime.setValueimportkotlinx.coroutines.CancellationExceptionimportkotlinx.coroutines.delayimportkotlin.math.log/** * This class represents a state holder for race participant. */classRaceParticipant(valname:String,valmaxProgress:Int100,valprogressDelayMillis:Long500L,privatevalprogressIncrement:Int1,privatevalinitialProgress:Int0){init{require(maxProgress0){maxProgress$maxProgress; must be 0}require(progressIncrement0){progressIncrement$progressIncrement; must be 0}}/** * Indicates the race participants current progress */varcurrentProgressbymutableStateOf(initialProgress)privateset/** * Regardless of the value of [initialProgress] the reset function will reset the * [currentProgress] to 0 */funreset(){currentProgress0}suspendfunrun(){try{// 为了模拟比赛添加一个while循环在没有完成比赛前一直循环while(currentProgressmaxProgress){// 使用挂起函数模拟比赛中的不同进度间隔。 延迟 函数执行会挂起不会阻止执行delay(progressDelayMillis)currentProgressprogressIncrement;}}catch(e:CancellationException){Log.e(RaceParticipant,$name:${e.message})throwe}}}/** * The Linear progress indicator expects progress value in the range of 0-1. This property * calculate the progress factor to satisfy the indicator requirements. */valRaceParticipant.progressFactor:Floatget()currentProgress/maxProgress.toFloat()在您刚刚添加完的代码时会在 Android Studio 中调用 delay() 函数的代码左侧看到一个图标如以下屏幕截图所此图标表示挂起点相应函数可能会挂起稍后再恢复运行。在协程等待延迟时间完成的过程中主线程不会阻塞如下图所示2.2. LaunchedEffect 方法当用户按Start按钮时 你需要对每个选手实例调用run()挂起函数从而开始比赛。因此你需要启动一个协程来调用run()函数。启动协程触发比赛时 您需要针对两位参赛者确保以下方面当用户点击Start按钮时即启动协程之时他们都会立刻开始跑当用户点击Pause或Reset按钮时即取消携程之时他们分别暂停或停止奔跑当用户关闭应用时系统会妥善管理取消操作即取消所有协程并将其绑定到一个生命周期。在之前你可能了解到您只能通过另外一个挂起函数调用挂起函数。如需从可组合箱内安全地调用挂起函数您需要使用LaunchedEffect()可组合项。只要还在组合中LaunchedEffect()可组合项就会运行提供的挂起函数。您可以使用LaunchedEffect()可组合函数实现下述所有目标借助LaunchedEffect()可组合项您可以安全从可组合项调用挂起函数当LaunchedEffect()函数进入组合时它会启动一个协程并将代码块作为参数传递。只要还在组合中它就会运行提供的挂起函数。当用户点Start按钮时LaunchedEffect()会进入组合并启动一个协程来说更新进度。当LaunchedEffect()退出组合时携程会被取消。在应用中如果用户点击了Pause/Reset按钮系统会从组合中移除LaunchedEffect()并取消底层协程。对于这个例子 您无需明确提供调度程序因为LaunchedEffect()会负责。ComposablefunRaceTrackerApp(){...varraceInProgressbyremember{mutableStateOf(false)}LaunchedEffect(playerOne,playerTwo){playerOne.run()playerTwo.run()}RaceTrackerScreen(...)}当你运行项目点击Start发现选手2是在选手1完成比赛后才开始跑的很显然这并不公平 我们应该并发启动。方法参数解释2.3. 结构化并发使用协程写代码的方式称为结构化并发。这种编程方式可以提高代码的可读性并缩短开发时间。结构化并发的思路是协程具有层次结构任务可以启动子任务子任务可以再启动子任务。这种层次结构的单位称为协程作用域。协程作用域应始终与生命周期相关联。协程 API 在设计上遵循这种结构化并发机制。您不能通过未标记为“挂起”的函数调用挂起函数。此限制可确保您从协程构建器中例如 launch调用挂起函数。这些构建器反过来又会与CoroutineScope相关联。为了让两位参赛者同时开跑 你需要启动两个单独的协程并将run函数的每次调用都移动到这些携程中。使用launch构建器封装对playerOne.run()的调用。LaunchedEffect(playerOne,playerTwo){launch{playerOne.run()}launch{playerTwo.run()}raceInProgressfalse}LaunchedEffect(可变变量){// 执行逻辑}// 注意 当变量变化时 会重新执行LaunchedEffect的逻辑。如果只想要执行一次 参数可以是不可变的或者直接写成Uint。你运行项目后点击Start虽然您认为比赛会开始但是按钮的文字立即意外的变回了Start。但是我们的实际要求应该是等两位选手都跑完了才将Pause按钮的文字重置为Start。现在之所以有这个问题是因为你启动两个协程后 就立即更新了比赛的标记 实际上是等两个协程完成后再更新标记。launch构建函数会启动一个协程来执行playerOne.run()然后立即返回以执行代码块中下一行执行playerOne.run()函数的第二个launch构建函数也会发生相同的执行流程。第二个launch构建器返回后raceInProgress标记会立即更新。2.4. 协程作用域coroutineScope挂起函数会创建一个CoroutineScope并通过当前作用域调用指定的挂起代码块。该作用域会继承LaunchedEffect()作用域中的coroutineContext当指定代码块及其所有子协程完成后作用域会立即返回。对于这个应用它会在两位参赛者对象都完成run()函数的执行后立即返回。 修改代码如下:ComposablefunRaceTrackerApp(){valplayerOneremember{RaceParticipant(namePlayer 1,progressIncrement1)}valplayerTworemember{RaceParticipant(namePlayer 2,progressIncrement2)}varraceInProgressbyremember{mutableStateOf(false)}if(raceInProgress){// 增加LaunchedEffect可组合选项LaunchedEffect(playerOne,playerTwo){// coroutineScope 挂起函数会创建一个 CoroutineScope// 并通过当前作用域调用指定的挂起代码块。该作用域会继承 LaunchedEffect() 作用域中的 coroutineContext。// 当指定的代码块及其所有子携程完成后作用域会立刻返回。对于RaceTracker应用它会在coroutineScope{launch{playerOne.run()}launch{playerTwo.run()}}raceInProgressfalse}}// ..... 其他实现}其流程如下当LaunchedEffect()代码块执行时控制权转移到coroutineScope{..}代码块。coroutineScope代码块以并发方式启动两个协程然后等待它们完成执行。执行完成后raceInProgress标志会更新。coroutineScope代码块只会在代码块内的所有代码均完成执行后才会返回并继续执行。对于代码块之外的代码是否存在并发仅仅是一个实现细节。这种编码方式为并发编程提供了一种结构化的方法被称为结构并发化。若要查看协程在用户点按 Reset 按钮时如何被取消请按以下步骤操作suspendfunrun(){try{while(currentProgressmaxProgress){// 使用挂起函数模拟比赛中的不同进度间隔。 延迟 函数执行会挂起不会阻止执行delay(progressDelayMillis)currentProgressprogressIncrement;}}catch(e:CancellationException){Log.e(RaceParticipant,$name:${e.message})throwe}}当比赛开始一段时间后点击Reset按钮 会抛出如下错误。2.5 rememberCoroutineScope一个获取“作用域对象”的 Composable 函数。这个对象本身并不执行任何代码只是为你提供了一个可以在 Composable 函数体之外如 onClick 回调启动协程的“容器”。它的关键价值在于生命周期管理这个 scope 与它在组合中的调用点绑定Composable 离开组合scope 就被取消。如果需要再用户交互按钮点击时启动协程就可以使用rememberCoroutineScope在 Compose 的按钮点击或其他 UI 事件回调中标准且安全的模式是:valscoperememberCoroutineScope()// 获取与UI生命周期绑定的ScopeButton(onClick{scope.launch{// 在事件回调中启动协程// 执行你的挂起操作例如显示Snackbar、发起网络请求等}})三. 单元测试3.1. 导入单元测试库需要特别留意使用协程的单元测试代码因为其执行可能是异步的并且可能发生在多个线程中。如需在测试中调用挂起函数您必须位于协程中。由于JUnit测试函数本身并不是挂起函数因此您需要使用runTest协程构建器。此构建器是kotlinx-coroutines-test库的一部分旨在用来执行测试。构建器会在新协程中执行测试主体。注意协程不仅可以直接在测试主体中启动还可以使用 runTest 通过测试中使用的对象启动。由于runTest是kotlinx-coroutines-test库的一部分因此您需要添加其依赖项。plugins{...}android{...}dependencies{...testImplementation(org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4)}然后点击顶部通知栏的Sync Now 这将完成导入和构建。3.2. 编写测试代码找到位于测试源代码集下的RaceParticipantTest.kt文件。在raceParticipant定义之后创建一个raceParticipant_RaceStarted_ProgressUpdated()函数并为其添加Test注解以定义测试。由于测试代码块需要放在runTest构建器中因此请使用表达式语法将runTest()代码块作为测试结果返回。/* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the License); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an AS IS BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */packagecom.example.racetrackerimportcom.example.racetracker.ui.RaceParticipantimportkotlinx.coroutines.ExperimentalCoroutinesApiimportkotlinx.coroutines.launchimportkotlinx.coroutines.test.advanceTimeByimportkotlinx.coroutines.test.runCurrentimportkotlinx.coroutines.test.runTestimportorg.junit.Assert.assertEqualsimportorg.junit.TestclassRaceParticipantTest{privatevalraceParticipantRaceParticipant(nameTest,maxProgress100,progressDelayMillis500L,initialProgress0,progressIncrement1)OptIn(ExperimentalCoroutinesApi::class)TestfunraceParticipant_RaceStarted_ProgressUpdated()runTest{// 添加只读的expectedProgress变量并将其设置为1valexpectedProgress1// 使用launch构建启动新携程并调用raceParticipant.run() 函数以模拟比赛开始的场景launch{raceParticipant.run()}// raceParticipant.progressDelayMillis 属性的值决定了多长时间后比赛进度才会更新// 为了在progressDelayMillis时间过后测试进度你需要再测试中添加某种形式的延迟// 使用advanceTimeBy()辅助函数将时间提前raceParticipant.progressDelayMillis的值。// advanceTimeBy() 函数有助于缩短测试执行时间advanceTimeBy(raceParticipant.progressDelayMillis)// 由于 advanceTimeBy() 不会运行原定在给定持续时间运行的任务// 因此您需要调用 runCurrent() 函数。此函数会在当前时间执行所有待处理的任务runCurrent()// 添加一个对 assertEquals() 函数的调用以检查 raceParticipant.currentProgress 属性的值// 是否与 expectedProgress 变量的值匹配以确保进度正确更新。assertEquals(expectedProgress,raceParticipant.currentProgress)}OptIn(ExperimentalCoroutinesApi::class)TestfunraceParticipant_RaceFinished_ProgressUpdated()runTest{launch{raceParticipant.run()}advanceTimeBy(raceParticipant.maxProgress*raceParticipant.progressDelayMillis)runCurrent()assertEquals(100,raceParticipant.currentProgress)}}
企业数字化 ERP 产品动态
相关推荐
Java学习路线图(2026最新版)| 从零基础到高级工程师,保姆级指南 Java学习路线图(2026最新版)| 从零基础到高级工程师,保姆级指南
前言:作为一名深耕Java开发多年的程序员,经常收到新手私信询问“Java该怎么学”“从哪里开始入手”“学完能找到工作吗”。其实Java学习没有捷径&#x… · 2026/11/11 8:20:50
C语言之扫雷游戏(功能扩展+代码解析!) /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/11 1:55:07
CLINE配置百炼平台coding plan大模型-待发布 1. 引入
在CLINE上,配置阿里的百炼平台,API Provider选择“Alibaba”反而会报错,该怎么配置才正常呢?
2. 配置
配置过程参考下图:如下两个是比较关键的参数:
(1)API Provider要选择“… · 2026/11/11 1:23:57
目前电工证复审上岗必须要这个证吗 电工证复审怎么搞?郑州报考避坑指南全在这里 之前考的证在外省能不能转过来?我认识的洛阳涧西区老李就遇到过这个难题,他在外地干了三年电工,回到郑州后才发现,证件不被本地认可。这让他在找工作、复审上都遇到了麻烦。如果你也在为 目前电工证复审… · 2026/11/15 19:37:09
工地太忙根本没时间复习考试?采煤机特种作业操作证+考前押题全攻略 工地太忙根本没时间复习考试?采煤机特种作业操作证+考前押题全攻略 工地太忙根本没时间复习考试?采煤机特种作业操作证是矿山、煤矿等作业人员必备的上岗凭证,但很多从业者因为工作强度大、时间紧张,总是在考试和现场操作之间来回拉扯。采煤机特种作业操… · 2026/11/15 19:37:09
哪里可以代审电工证全流程办理指南 电工证外省考的能转吗?郑州报考避坑指南全解析 之前考的证在外省能不能转过来?这几乎是每个准备转行或提升技能的特种作业从业者最关心的问题。尤其是在郑州,很多小伙伴在外地工作多年,拿着异地的电工证,回到本地后才发现证书不被认可,甚至面临重新考证… · 2026/11/15 19:37:09
新西兰女电工证怎么考官方回应外省证怎么转 新西兰女电工证怎么考官方回应外省证怎么转 之前考的证在外省能不能转过来?这几乎是所有特种作业持证人最关心的问题之一,尤其是像新西兰女电工证这种在国际上也逐渐被认可的证书,很多人在跨省、跨国工作时都面临同样的疑惑。 官方回应… · 2026/11/15 19:37:09
平邑特种电工证怎么考?郑州报考避坑指南全在这了 平邑特种电工证怎么考?郑州报考避坑指南全在这了 你是不是学历不高,担心自己报不了名?别急,今天就来聊聊【平邑特种电工证】到底怎么考,还有郑州报考的避坑指南,让你少走弯路。… · 2026/11/15 19:37:09
宝应电工证在哪里办和考前押题怎么用才不吃亏 宝应电工证在哪里办和考前押题怎么用才不吃亏 之前考的证在外省能不能转过来?这个问题在宝应很多学员身上都出现过,尤其是从其他省份来这边打工或创业的电工,手里拿着外省的证书,却不知道怎么在当地使用。这事儿说白了, 宝应电工证在哪里办… · 2026/11/15 19:36:09
长春初级电工证怎么考?通过率揭秘全在这里 长春初级电工证怎么考?通过率揭秘全在这里 之前考的证在外省能不能转过来?这几乎是每个想从事电工工作的人都会问的问题。特别是那些在外地打拼多年、手里握着一张“外地电工证”的人,到了长春想继续干这一行,第一反应就是“我的证能用吗?”。今天我们就… · 2026/11/15 0:00:50
国内电工证报考条件与考试时间 低压电工证怎么考?学历不高也能报名,官方报名入口全在这 你是不是经常听说“电工证是入行门槛”,但又担心自己学历不够、经验不足,根本报不了名?别急,今天就带你从零了解【国内电工证】的报考流程、考试内容,还有怎么在 官方报名入口… · 2026/11/15 0:00:50
电工作业时如何断电?电子证书到底能不能全国通用? 电工作业时如何断电?电子证书到底能不能全国通用? 实操考试心里没底怕挂科,特别是电工作业时如何断电这一操作,不仅关系到你能不能拿到证书,更关系到以后能不能顺利上岗。很多人觉得这不过就是“断个电”,但其实这里面的门道可不少,稍有不慎就可能出错… · 2026/11/15 0:00:50
长春初级电工证怎么考?通过率揭秘全在这里 长春初级电工证怎么考?通过率揭秘全在这里 之前考的证在外省能不能转过来?这几乎是每个想从事电工工作的人都会问的问题。特别是那些在外地打拼多年、手里握着一张“外地电工证”的人,到了长春想继续干这一行,第一反应就是“我的证能用吗?”。今天我们就… · 2026/11/15 0:00:50
国内电工证报考条件与考试时间 低压电工证怎么考?学历不高也能报名,官方报名入口全在这 你是不是经常听说“电工证是入行门槛”,但又担心自己学历不够、经验不足,根本报不了名?别急,今天就带你从零了解【国内电工证】的报考流程、考试内容,还有怎么在 官方报名入口… · 2026/11/15 0:00:50
电工作业时如何断电?电子证书到底能不能全国通用? 电工作业时如何断电?电子证书到底能不能全国通用? 实操考试心里没底怕挂科,特别是电工作业时如何断电这一操作,不仅关系到你能不能拿到证书,更关系到以后能不能顺利上岗。很多人觉得这不过就是“断个电”,但其实这里面的门道可不少,稍有不慎就可能出错… · 2026/11/15 0:00:50
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/15 1:51:20
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/15 4:02:19
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/15 3:02:05