kotlin知识体系(五) :Android 协程全解析,从作用域到异常处理的全面指南
1. 什么是协程
协程(Coroutine)是轻量级的线程,支持挂起和恢复,从而避免阻塞线程。

2. 协程的优势
协程通过结构化并发和简洁的语法,显著提升了异步编程的效率与代码质量。
2.1 资源占用低(一个线程可运行多个协程)
传统多线程模型中,每个线程需要独立的系统资源(如内存栈),而协程共享线程资源。
- 高效线程利用:通过调度器(如 Dispatchers.IO),一个线程池可同时处理数千个协程任务(如并发网络请求或文件读写)。
- 减少上下文切换:协程挂起时不会阻塞线程,线程可立即执行其他协程任务,减少线程切换的性能损耗。
2.2 代码可读性强(顺序编写异步逻辑)
协程通过同步代码风格实现异步逻辑,彻底消除“回调地狱”。
- 同步化表达:使用挂起函数(如 withContext、await())可将异步操作写成顺序执行的代码。
- 结构化并发:通过 CoroutineScope 管理协程生命周期,自动取消子协程,避免内存泄漏。
3. 协程的核心组件
协程通过一组核心组件实现结构化并发和高效的任务管理。
3.1 CoroutineScope(作用域)
- 管理协程的生命周期,确保协程在特定范围内启动和取消。
- 通过结构化并发避免资源泄漏。
3.1.1 常见作用域:
- lifecycleScope : 与 Lifecycle(如 Activity/Fragment)绑定,界面销毁时自动取消所有子协程。
- viewModelScope : 与 ViewModel 绑定,ViewModel 销毁时自动取消协程,适合处理业务逻辑。
3.2 CoroutineContext(上下文)
定义协程的上下文信息,如线程调度器、协程名称、异常处理器等。
- Job : 控制协程的生命周期(启动、取消、监控状态)
- Dispatcher : 指定协程运行的线程
- CoroutineName : 为协程命名,便于调试
3.3 Dispatcher (调度器)
指定协程运行的线程
- Dispatchers.Main : 主线程,用于更新 UI 或执行轻量级操作。
注意:在非 Android 环境(如单元测试)中可能不存在。 - Dispatchers.IO : 适用于 IO 密集型任务(如网络请求、数据库读写、文件操作)。
底层机制:共享线程池,默认最小 64 线程。 - Dispatchers.Default : 适用于 CPU 密集型任务(如排序、计算、图像处理)。
底层机制:线程数与 CPU 核心数相同。
3.4 Job (作业)
3.4.1 Job (作业)
表示一个协程任务,不返回结果,通过 launch 创建。
val job = launch { /* ... */ }
job.start() // 启动(默认自动启动)
job.cancel() // 取消
job.join() // 挂起当前协程,等待此 Job 完成
3.4.2 Deferred (异步结果)
Job 的子类,表示一个会返回结果的异步任务,通过 async 创建。
val deferred = async { fetchData() }
val data = deferred.await() // 挂起协程直到结果就绪
4. 协程构建器
协程构建器是创建和启动协程的入口点,不同构建器适用于不同场景。
4.1 launch:启动一个不返回结果的协程
启动一个不返回结果的协程,适用于“触发后无需等待结果”的任务(如日志上报、缓存清理)。
特性
- 返回 Job 对象,用于控制协程生命周期(取消、监控状态)。
- 默认继承父协程的上下文(如作用域、调度器)。
// 在 ViewModel 中启动一个后台任务
fun startBackgroundTask() {viewModelScope.launch(Dispatchers.IO) { cleanCache() // 在 IO 线程执行清理操作log("Cache cleaned") // 完成后记录日志}// 无需等待结果,直接执行后续代码
}
4.2 async:并发执行并获取结果
启动一个返回结果的协程,适用于需要并行执行多个任务并汇总结果的场景。
特性:
- 返回 Deferred 对象,通过 await() 挂起并获取结果。
- 可通过 async 启动多个协程后统一等待结果,提升执行效率。
示例:并行请求多个接口并合并数据
viewModelScope.launch {// 同时发起两个网络请求val userDeferred = async(Dispatchers.IO) { fetchUser() } val postsDeferred = async(Dispatchers.IO) { fetchPosts() }// 等待两个请求完成(总耗时取决于最慢的任务)val user = userDeferred.await() val posts = postsDeferred.await()// 合并结果并更新 UIshowUserProfile(user, posts)
}
4.3 runBlocking:在阻塞代码中启动协程
阻塞当前线程,直到其内部的协程执行完毕。
主要用于测试,或在非协程环境中临时调用挂起函数。
示例:在单元测试中测试协程逻辑
@Test
fun testFetchData() = runBlocking { // 阻塞当前线程,等待协程完成val data = fetchData() // 直接调用挂起函数assertEquals(expectedData, data)
}
应避免在主线程使用 runBlocking,因为会阻塞主线程 !
5. 挂起函数
挂起函数(Suspending Function)是协程的核心特性之一,允许协程在非阻塞的前提下暂停和恢复执行。挂起函数只能在协程或其他挂起函数中调用,适用于需要等待异步操作完成的场景。
5.1 delay():协程的“非阻塞休眠”
delay() 会暂停协程的执行指定时间(单位:毫秒),期间不会阻塞线程,线程可执行其他任务。
5.1.1 与 Thread.sleep() 的区别:
| delay() | Thread.sleep() |
|---|---|
| 挂起协程,释放线程资源 | 阻塞线程,线程无法执行其他任务 |
| 只能在协程或挂起函数中调用 | 可在任何线程中调用 |
viewModelScope.launch {repeat(10) { delay(1000) // 每隔 1 秒执行一次,不阻塞主线程 updateCounter(it) }
}
5.2 withContext():灵活的线程切换
在指定协程上下文(如 Dispatcher)中执行代码块,完成后自动恢复原上下文。替代传统回调或 Handler,简化线程切换逻辑。
suspend fun loadData() { // 在 IO 线程执行网络请求 val data = withContext(Dispatchers.IO) { api.fetchData() } // 自动切回调用方的上下文(如 Main 线程) updateUI(data)
}
5.2.1 与 async 的区别
- withContext:直接返回结果,适用于单次切换线程的串行任务。
- async:返回 Deferred,适用于并行任务。
避免嵌套多层 withContext,可用 async 替代以提升并发效率。
5.3 await():安全获取异步结果
挂起协程,等待 Deferred 任务完成并返回结果。若 Deferred 任务出现异常,await() 会抛出该异常。
5.3.1 示例:并行任务与结果合并
viewModelScope.launch { val task1 = async(Dispatchers.IO) { fetchDataA() } val task2 = async(Dispatchers.IO) { fetchDataB() } // 同时等待两个任务完成 val combinedData = combineData(task1.await(), task2.await())
}
5.3.2 await()怎么处理异常
使用 try-catch 捕获 await() 的异常:
val deferred = async { /* 可能抛出异常的代码 */ }
try { val result = deferred.await()
} catch (e: Exception) { handleError(e)
}
若需取消任务,调用 deferred.cancel()
5.3.3 协程是否存活
通过 coroutineContext.isActive 检查协程是否存活,可以及时终止无效操作
suspend fun heavyCalculation() { withContext(Dispatchers.Default) { for (i in 0..100000) { if (!isActive) return@withContext // 检查协程是否被取消 // 执行计算 } }
}
6. 协程的异常处理机制
6.1 异常传播机制
默认规则:
- 子协程异常会向上传播:当子协程抛出未捕获的异常时,父协程会立即取消,进而取消所有其他子协程。
- 兄弟协程受影响:若子协程 A 抛出异常,其兄弟协程 B 也会被取消,即使 B 仍在执行中。
示例:未捕获异常导致父协程取消
viewModelScope.launch {// 子协程 1launch {delay(100)throw IOException("网络请求失败") // 未捕获异常}// 子协程 2(会被父协程取消)launch {repeat(10) {delay(200)log("子任务执行中") // 仅执行 1 次后父协程取消}}
}
6.2 捕获异常的方式
6.2.1 方式 1:try-catch 块
在协程内部直接捕获异常,适用于同步代码逻辑。
viewModelScope.launch {try {fetchData() // 可能抛出异常的挂起函数} catch (e: IOException) {showError("网络异常: ${e.message}")}
}
6.2.2 方式 2:CoroutineExceptionHandler
全局异常处理器,用于捕获未通过 try-catch 处理的异常。
定义异常处理器
val exceptionHandler = CoroutineExceptionHandler { _, throwable ->log("未捕获异常: ${throwable.message}")showErrorToast() // 例如弹出 Toast
}
附加到协程上下文
viewModelScope.launch(exceptionHandler) { launch { throw IOException() } // 异常会被 exceptionHandler 捕获
}
仅在根协程(直接通过 launch 或 async 创建的顶层协程)中生效。
6.3 隔离异常:SupervisorJob
阻止子协程的异常传播到父协程,避免“一颗老鼠屎坏了一锅粥”。常用于独立任务场景(如同时发起多个不相关的网络请求)。
- 子协程的失败不会影响其他子协程。
- 父协程仍会等待所有子协程完成(除非显式取消)。
6.3.1 通过 SupervisorJob() 创建作用域
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
scope.launch { throw Exception() } // 不影响其他子协程
scope.launch { delay(1000) } // 正常执行
6.3.2 在现有作用域中使用 supervisorScope
viewModelScope.launch {supervisorScope {launch { throw IOException() } // 仅自身失败launch { delay(1000) } // 继续执行}
}
6.4 自定义异常处理策略
可以根据业务需求设计容错逻辑,例如:
- 重试机制:在捕获异常后自动重试任务。
- 回退操作:失败时返回默认值或缓存数据。
6.4.1 示例:网络请求重试
suspend fun fetchDataWithRetry(retries: Int = 3): Data {repeat(retries) { attempt ->try {return api.fetchData()} catch (e: IOException) {if (attempt == retries - 1) throw e // 最后一次重试仍失败则抛出异常delay(1000 * (attempt + 1)) // 延迟后重试(指数退避)}}throw IllegalStateException("Unreachable")
}
6.5 协程异常的最佳实践
6.5.1 明确异常边界
- 在协程根节点或关键入口处统一处理异常(如使用 CoroutineExceptionHandler)。
- 避免在底层函数中静默吞没异常(如 catch 后不处理)。
6.5.2 区分取消与异常
- 使用 isActive 检查协程状态,及时终止无效任务。
- 通过 ensureActive() 快速失败
public fun Job.ensureActive(): Unit {if (!isActive) throw getCancellationException()
}
6.5.3 谨慎使用 SupervisorJob
仅当子协程完全独立时使用,避免隐藏潜在问题。
相关文章:
kotlin知识体系(五) :Android 协程全解析,从作用域到异常处理的全面指南
1. 什么是协程 协程(Coroutine)是轻量级的线程,支持挂起和恢复,从而避免阻塞线程。 2. 协程的优势 协程通过结构化并发和简洁的语法,显著提升了异步编程的效率与代码质量。 2.1 资源占用低(一个线程可运行多个协程)…...
vscode stm32 variable uint32_t is not a type name 问题修复
问题 在使用vscodekeil开发stm32程序时,发现有时候vscode的自动补全功能失效,且problem窗口一直在报错。variable “uint32_t” is not a type name uint32_t 定义位置 uint32_t 实际是在D:/Keil_v5/ARM/ARMCC/include/stdint.h中定义的。将D:/Keil_v5…...
Formality:Bug记录
相关阅读 Formalityhttps://blog.csdn.net/weixin_45791458/category_12841971.html?spm1001.2014.3001.5482 本文记录博主在使用Synopsys的形式验证工具Formality中遇到的一个Bug。 Bug复现 情况一 // 例1 module dff (input clk, input d_in, output d_out …...
在ubuntu20.04+系统部署VUE及Django项目的过程记录——以腾讯云为例
目录 1. 需求2. 项目准备3. VUE CLI项目部署3.1 部署前的准备3.1.1 后端通信路由修改3.1.2 导航修改 3.2 构建项目3.3 配置nginx代理 4. 后端配置4.1 其他依赖项4.2 单次执行测试4.3 创建Systemd 服务文件4.4 配置 Nginx 作为反向代理 5. 其他注意事项 1. 需求 近期做一些简单…...
回归,git 分支开发操作命令
核心分支说明 主分支(master/production)存放随时可部署到生产环境的稳定代码,仅接受通过测试的合并请求。 开发分支(develop)集成所有功能开发的稳定版本,日常开发的基础分支,从该分支创建特性…...
【java+Mysql】学生信息管理系统
学生信息管理系统是一种用于管理学生信息的软件系统,旨在提高学校管理效率和服务质量。本课程设计报告旨在介绍设计和实现学生信息管理系统的过程。报告首先分析了系统的需求,包括学生基本信息管理、成绩管理等功能。接着介绍了系统的设计方案࿰…...
小白从0学习网站搭建的关键事项和避坑指南(2)
以下是针对小白从零学习网站搭建的 进阶注意事项和避坑指南(第二期),覆盖开发中的高阶技巧、常见陷阱及解决方案,帮助你在实战中提升效率和质量: 一、进阶技术选型避坑 1. 前端框架选择 误区:盲目追求最新…...
Windows 10 上安装 Spring Boot CLI详细步骤
在 Windows 10 上安装 Spring Boot CLI 可以通过以下几种方式完成。以下是详细的步骤说明: 1. 手动安装(推荐) 步骤 1:下载 Spring Boot CLI 访问 Spring Boot CLI 官方发布页面。下载最新版本的 .zip 文件(例如 sp…...
spring boot -- 配置文件application.properties 换成 application.yml
在Spring Boot项目中,application.properties和application.yml是两种常用的配置文件格式,它们各自具有不同的特点和适用场景2。以下是它们之间的主要差异2: 性能差异 4: 加载机制 2: application.properties文件会被加载到内存中,并且只加载一次,之后直接从内存中读取…...
Spring Boot实战:基于策略模式+代理模式手写幂等性注解组件
一、为什么需要幂等性? 核心定义:在分布式系统中,一个操作无论执行一次还是多次,最终结果都保持一致。 典型场景: 用户重复点击提交按钮网络抖动导致的请求重试消息队列的重复消费支付系统的回调通知 不处理幂等的风…...
【Rust 精进之路之第14篇-结构体 Struct】定义、实例化与方法:封装数据与行为
系列: Rust 精进之路:构建可靠、高效软件的底层逻辑 作者: 码觉客 发布日期: 2025-04-20 引言:超越元组,给数据赋予意义 在之前的学习中,我们了解了 Rust 的基本数据类型(标量)以及两种基础的复合类型:元组 (Tuple) 和数组 (Array)。元组允许我们将不同类型的值组合…...
postgres 数据库信息解读 与 sqlshell常用指令介绍
数据库信息: sqlshell Server [localhost]: 192.168.30.101 Database [postgres]: Port [5432]: 5432 Username [postgres]: 用户 postgres 的口令: psql (15.12, 服务器 16.8 (Debian 16.8-1.pgdg120+1)) 警告:psql 主版本15,服务器主版本为16.一些psql功能可能无法正常使…...
论文阅读:2024 arxiv DeepInception: Hypnotize Large Language Model to Be Jailbreaker
总目录 大模型安全相关研究:https://blog.csdn.net/WhiffeYF/article/details/142132328 DeepInception: Hypnotize Large Language Model to Be Jailbreaker DeepInception:催眠大型语言模型,助你成为越狱者 https://arxiv.org/pdf/2311.0…...
vue2技术练习-开发了一个宠物相关的前端静态商城网站-宠物商城网站
为了尽快学习掌握相关的前端技术,最近又实用 vue2做了一个宠物行业的前端静态网站商城。还是先给大家看一下相关的网站效果: 所以大家如果想快速的学习或者掌握一门编程语言,最好的方案就是通过学习了基础编程知识后,就开始利用…...
嵌入式学习——远程终端登录和桌面访问
目录 通过桥接模式连接虚拟机和Windows系统 1、桥接模式 2、虚拟机和Windows连接(1) 3、虚拟机和Windows连接(2) 在Linux虚拟机中创建新用户 Windows系统环境下对Linux系统虚拟机操作 远程登录虚拟机(1ÿ…...
wpf stylet框架 关于View与viewmodel自动关联绑定的问题
1.1 命名规则 Aview 对应 AVIewModel, 文件夹 views 和 viewmodels 1.2 需要注册服务 //RootViewModel是主窗口 public class Bootstrapper : Bootstrapper<RootViewModel>{/// <summary>/// 配置IoC容器。为数据共享创建服务/// </summary…...
如何新建一个空分支(不继承 master 或任何提交)
一、需求分析: 在 Git 中,我们通常通过 git branch 来新建分支,这些分支默认都会继承当前所在分支的提交记录。但有时候我们希望新建一个“完全干净”的分支 —— 没有任何提交,不继承 master 或任何已有内容,这该怎么…...
HarmonyOS-ArkUI-动画分类简介
本文的目的是,了解一下HarmonyOS动画体系中的分类。有个大致的了解即可。 动效与动画简介 动画,是客户端提升界面交互用户体验的一个重要的方式。可以使应用程序更加生动灵越,提高用户体验。 HarmonyOS对于界面的交互方面,围绕回归本源的设计理念,打造自然,流畅品质一提…...
Qt编写推流程序/支持webrtc265/从此不用再转码/打开新世界的大门
一、前言 在推流领域,尤其是监控行业,现在主流设备基本上都是265格式的视频流,想要在网页上直接显示监控流,之前的方案是,要么转成hls,要么魔改支持265格式的flv,要么265转成264,如…...
[第十六届蓝桥杯 JavaB 组] 真题 + 经验分享
A:逃离高塔(AC) 这题就是简单的签到题,按照题意枚举即可。需要注意的是不要忘记用long,用int的话会爆。 📖 代码示例: import java.io.*; import java.util.*; public class Main {public static PrintWriter pr ne…...
深⼊理解 JVM 执⾏引擎
深⼊理解 JVM 执⾏引擎 其中前端编译是在 JVM 虚拟机之外执⾏,所以与 JVM 虚拟机没有太⼤的关系。任何编程语⾔,只要能够编译出 满⾜ JVM 规范的 Class ⽂件,就可以提交到 JVM 虚拟机执⾏。⾄于编译的过程,如果你不是想要专⻔去研…...
iwebsec靶场 文件包含关卡通关笔记11-ssh日志文件包含
目录 日志包含 1.构造恶意ssh登录命令 2.配置ssh日志开启 (1)配置sshd (2)配置rsyslog (3)重启服务 3.写入webshell木马 4.获取php信息渗透 5.蚁剑连接 日志包含 1.构造恶意ssh登录命令 ssh服务…...
kafka菜鸟教程
一、kafka原理 1、kafka是一个高性能的消息队列系统,能够处理大规模的数据流,并提供低延迟的数据传输,它能够以每秒数十万条消息的速度进行读写操作。 二、kafka优点 1、服务解耦 (1)提高系统的可维护性 通过服务…...
应用镜像是什么?轻量应用服务器的镜像大全
应用镜像是轻量应用服务器专属的,镜像就是轻量应用服务器的装机盘,应用镜像在原有的纯净版操作系统上集成了应用程序,例如WordPress应用镜像、宝塔面板应用镜像、WooCommerce等应用,阿里云服务器网aliyunfuwuqi.com整理什么是轻量…...
深入理解分布式缓存 以及Redis 实现缓存更新通知方案
一、分布式缓存简介 1. 什么是分布式缓存 分布式缓存:指将应用系统和缓存组件进行分离的缓存机制,这样多个应用系统就可以共享一套缓存数据了,它的特点是共享缓存服务和可集群部署,为缓存系统提供了高可用的运行环境,…...
Spring Boot 中的自动配置原理
2025/4/6 向全栈工程师迈进! 一、自动配置 所谓的自动配置原理就是遵循约定大约配置的原则,在boot工程程序启动后,起步依赖中的一些bean对象会自动的注入到IOC容器中。 在讲解Spring Boot 中bean对象的管理的时候,我们注入bean对…...
软考高级-系统架构设计师 论文范文参考(一)
文章目录 论SOA技术的应用论SOA在企业信息化中的应用论UP(统一过程方法)的应用论分布式数据库的设计与实现论改进Web服务器性能的有关技术论基于UML的需求分析论基于构件的软件开发论基于构件的软件开发(二) 论SOA技术的应用 摘要: 本人于…...
剑指Offer(数据结构与算法面试题精讲)C++版——day16
剑指Offer(数据结构与算法面试题精讲)C版——day16 题目一:序列化和反序列化二叉树题目二:从根节点到叶节点的路径数字之和题目三:向下的路径节点值之和附录:源码gitee仓库 题目一:序列化和反序…...
windows server C# IIS部署
1、添加IIS功能 windows server 2012、windows server 2016、windows server 2019 说明:自带的是.net 4.5 不需要安装.net 3.5 尽量使用 windows server 2019、2016高版本,低版本会出现需要打补丁的问题 2、打开IIS 3、打开iis应用池 .net 4.5 4、添…...
Android: gradient 使用
在 Android 中使用 gradient(渐变) 通常是通过 drawable 文件来设置背景。下面是可以直接用的几种用法汇总,包括线性渐变、径向渐变、扫描渐变(sweep)等: ✅ 1. Linear Gradient(线性渐变&#…...
