一,什么是协程:
官方的介绍:
协程是一种并发设计模式,在Android平台上可以使用协程简化异步执行代码。(协程可以使我们是同步的代码形式实现异步任务的执行)
协程的特点:
- 轻量:一个线程上可以运行多个协程,协程支持挂起,协程的挂起不会阻塞运行协程的线程。挂起比阻塞更节省内存,也能让线程得到充分的利用。
- 内存泄漏更少:使用结构化并发在一个范围内(协程作用域)运行多项操作。
- 内置取消支持:取消通过正在运行的协程层次结构,自动传播。
- Jetpack集成:JetPack库提供了全面支持协程的扩展,提供了类似LifecycleScope,ViewModelScope绑定生命周期的作用域。
二,协程的使用:
1,添加相关依赖:
project的build.gradle中添加Kotlin编译插件
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.32"
}
app的build.gradle中添加协程相关的依赖库。
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.4.32"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3"
}
2,构建启动一个协程
构建启动协程常用三种方式:
runBlocking:创建并启动一个新的线程,并且会阻塞当前的线程,直到协程内所有逻辑 和 子协程都执行完毕。项目中用不了,主要在@test测试代码中使用。
launch: 启动创建一个新的协程而不阻塞当前的线程。返回一个协程引用Job对象。
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job
context: 协程的上下文,协程的运行环境。可以是多个上下文组合,包括调度器(Dispatchers.Default)、协程本身的job、协程的名称(CoroutineName)、异常处理器(CoroutineExceptionHandler)
start: 协程的启动模式,立即启动 或 懒启动(Lazy)
block:是一个无参、无返回值的挂起函数,我们业务逻辑实现方法。
Job:协程的任务对象,可以通过Job 获取协程的状态和对协程执行操作。isActive()判断是否活跃,start()启动协程,cancel取消协程,join()挂起协程直到此job执行完成等,
fun test(){
val job = MainScope().launch{
Log.i("wang", "launch")
delay(1000)//挂起函数
Log.i("wang", "after one second")
//两个launch构建并启动
val job1= launch {
delay(3000)
Log.i("wang", "launch1")
}
launch {
delay(3000)
Log.i("wang", "launch2")
}
job1.join()//等待协程执行完,也是挂起函数,非阻塞的
launch {
delay(2000)
Log.i("wang", "launch3")
}
}
Log.i("wang", "end")
}
运行结果:
2025-04-28 09:53:32.449 14296-14296 wang I end
2025-04-28 09:53:32.452 14296-14296 wang I launch
2025-04-28 09:53:33.454 14296-14296 wang I after one second
2025-04-28 09:53:36.467 14296-14296 wang I launch1
2025-04-28 09:53:36.471 14296-14296 wang I launch2
2025-04-28 09:53:38.480 14296-14296 wang I launch3
async:启动创建一个新的协程而不阻塞当前的线程。返回一个协程引用Deffered对象。
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T>
block: 无参、有返回值T的挂起函数,较launch多了返回值。
Deffered: 是Job的子类,较Job多了个await()挂起函数,用于等待协程执行的结果返回值。
fun testAsync(){
MainScope().launch {
val deferred1 = async {
delay(1000)
Log.i("wang", "deferred1 execute")
100
}
val deferred2 = async {
delay(2000)
Log.i("wang", "deferred2 execute")
200
}
val deferred3 = async {
delay(3000)
Log.i("wang", "deferred3 execute")
300
}
Log.i("wang", "sum = " + (deferred1.await() + deferred2.await() + deferred3.await()))
}
}
运行结果:
2025-04-27 18:28:37.134 9747-9747 wang com.example.myapplication I deferred1 execute
2025-04-27 18:28:38.134 9747-9747 wang com.example.myapplication I deferred2 execute
2025-04-27 18:28:39.136 9747-9747 wang com.example.myapplication I deferred3 execute
2025-04-27 18:28:39.137 9747-9747 wang com.example.myapplication I sum = 600
3,协程的作用域
GlobalScope:作用域是全局,与应用程序的生命周期绑定。
coroutineScope: 创建一个独立协程作用域,所有的子协程都执行完了以后才结束自身,当前有一个子协程抛出异常后,所有未执行完成的子协程+自身都会停止执行。
superVisorScope: 同上类似,不同是子协程出现异常,不会影响父协程,也不会影响同阶的子协程。前提是你的子协程自己处理了异常(有CoroutineExceptionHandler)。
MainScope():上下文是SupervisorJob() + Dispatchers.Main。一个在主线程执行的协程作用域。
lifecycleScope:具有生命周期感知的协程作用域,与Lifecycle生命周期绑定,生命周期结束时,此作用域将被取消。
viewModelScope: 与lifecycleScope类似,与ViewModel的生命周期绑定。
MainScope().launch {
try {
coroutineScope {
Log.i("wang" , "coroutineScope execute")
launch {
throw Exception(" coroutineScope 子协程失败")
}
launch {
delay(100)
Log.i("wang","因为其他子协程失败,这段代码不会被打印。")
}
}
} catch (e: Exception) {
Log.i("wang" , "捕获异常: ${e.message}")
}
try {
supervisorScope {
Log.i("wang" , "supervisorScope execute")
launch(coroutineExceptionHandler()) {
throw Exception("supervisorScope 子协程失败")
}
launch {
delay(100)
Log.i("wang","尽管其他子协程失败了,这段代码仍然会被打印。")
}
}
} catch (e: Exception) {
Log.i("wang" , "捕获异常: ${e.message}")
}
}
输出结果如下:
18:25:17.253 wang com.example.myapplication I coroutineScope execute
18:25:17.255 wang com.example.myapplication I 捕获异常: coroutineScope 子协程失败
18:25:17.255 wang com.example.myapplication I supervisorScope execute
18:25:17.255 wang com.example.myapplication I exceptionHandler supervisorScope 子协程失败
18:25:17.356 wang com.example.myapplication I 尽管其他子协程失败了,这段代码仍然会被打印。
4,协程调度器
Kotlin 提供了四个调度器,您可以使用它们来指定应在何处运行协程:
| 调度器 | 介绍 |
|---|---|
| Dispatchers.Default | 默认调度器,非主线程。CPU密集型任务调度器。用于json解析,数据处理 |
| Dispatchers.Main | UI调度器, Andorid 上的主线程。用于UI更新 |
| Dispatchers.Unconfined | 非指定线程的协程调度器,取决于启动的线程。 |
| Dispatchers.IO | IO调度器,非主线程,执行的线程是IO线程。用于网络请求,数据库处理、文件读写 |
5,suspend挂起函数
suspend:是kotlin的核心关键字,使用suspend关键字修饰的函数叫挂起函数,挂起函数只能在协程中或其他挂起函数中执行。
协程在执行到有suspend标记的挂起函数时,当前函数会被挂起(暂停),直到该挂起函数内部逻辑完成,才会在挂起的地方resume恢复继续执行,而不会阻塞运行协程的线程。
private fun testSuspend(){
//启动了一个协程,去执行一个挂起函数
Log.i("wang", "start")
MainScope().launch {
Log.i("wang", "start")
val result = doWorkBackGroup()
Log.i("wang", "result$result")
}
}
private suspend fun doWorkBackGroup(): String{
//模拟耗时任务
delay(1000)
Log.i("wang", "doWorkBackGroup end")
return "hello world"
}
执行结果如下:
2025-04-28 17:34:21.049 27706-27706 wang I start
2025-04-28 17:34:22.051 27706-27706 wang I doWorkBackGroup end
2025-04-28 17:34:22.052 27706-27706 wang I resulthello world
源码分析:KotlinBytecode后在deCompile后的代码如下,多了一个编译器加的参数Continuation,有个接口方法resumeWith(result), 即挂起函数执行完毕后恢复的回调。
private final Object doWorkBackGroup(Continuation $completion) {
@kotlin.SinceKotlin public interface Continuation<in T> {
public abstract val context: kotlin.coroutines.CoroutineContext
public abstract fun resumeWith(result: kotlin.Result<T>): kotlin.Unit
}
6,suspendCancellableCoroutine 函数的使用
在Android开发中将回调(Callback)改写成协程(Coroutine)的方式,会使的异步代码更简洁、易读。用例如下:
// 原始回调接口
interface Callback {
fun onSuccess(result: String)
fun onError(error: Throwable)
}
// 将回调改写成挂起函数,suspendCancellableCoroutine挂起当前函数等待回调,支持取消
suspend fun fetchData(): String = suspendCancellableCoroutine { continuation ->
val callback = object : Callback {
override fun onSuccess(result: String) {
continuation.resume(result) // 成功时恢复协程
}
override fun onError(error: Throwable) {
continuation.resumeWithException(error) // 失败时抛出异常
}
}
// 调用原始异步方法
performAsyncOperation(callback)
// 处理协程取消(可选)
continuation.invokeOnCancellation {
// 这里可取消原始操作(如网络请求),需要自己实现
cancelAsyncOperation()
}
}
// 示例使用
viewModelScope.launch {
try {
val result = fetchData() // 同步写法
showResult(result)
} catch (e: Exception) {
showError(e)
}
}
总结具有如下优点:
- 消除回调地狱:异步代码线性执行
- 统一错误处理:使用 try/catch 捕获异常
- 生命周期安全:配合 viewModelScope/lifecycleScope 自动取消
- 取消传播:协程取消自动触发请求取消
注意事项:
- 确保每次回调 只调用一次 resume/resumeWithException
- 如果底层操作不支持取消,invokeOnCancellation 可省略
- 避免在回调中使用已取消的 continuation(通过 isActive 检查)
通过这种方式,你可以将任何基于回调的异步 API(网络请求、数据库操作、蓝牙交互等)无缝集成到 Kotlin 协程的同步编程模型中。
7,kotlin Flow的使用
Kotlin 协程库中异步流数据处理的响应式编程框架。基于协程实现的,提供了更简洁,更具kotlin风格的编程方式。
Flow核心特点:
- Flow 默认是冷流,但是StateFlow与SharedFlow是热流。
- 支持协程的取消和上下文传播
- 提供了丰富的操作符
- 通过协程挂起处理背压
1.1 冷流(cold-stream):每次收集(collect)时,启动新的数据流。 1.2.1,Flow的创建
//Flow的构建器
fun simpleFlow(): Flow<Int> = flow {
for (i in 1..3) {
delay(100) // 模拟异步操作
emit(i) // 发射值
}
}
1.2.2,在协程作用域中接口数据
// 在协程作用域内收集
viewModelScope.launch {
simpleFlow().collect { value ->
println(value) // 输出: 1, 2, 3 (间隔100ms)
}
}
2.1 Flow的收集会响应协程的取消请求。(Flow的收集是在协程作用域中,emit函数的是挂起函数)。
// 示例:流收集的自动取消
val job = launch {
flow {
repeat(10) { i ->
delay(500)
emit(i)
if (i == 3) cancel() // 测试取消
}
}.collect { println(it) }
}
// 输出: 0, 1, 2, 3 → 随后流被取消
3.1 中间操作符(返回新的flow):
.map { it * 2 } // 转换值
.filter { it > 5 } // 过滤
.transform { ... } // 复杂转换
.take(3) // 取前N个
.flowOn(Dispatchers.IO) // 指定上游上下文
3.2 终止操作符(启动数据流):
.collect { println(it) } // 收集值
.toList() // 转为集合
.first() // 获取第一个值
.reduce { a, b -> a + b }// 聚合
3.3 异常处理:
.catch { e ->
println("Caught $e")
emit(-1) // 异常时发射备用值
}
.onCompletion { cause -> // 流完成时回调
if (cause != null) println("Flow failed")
}
4,背压(backpressure)策略:Flow的生产者和消费者通过携程的挂起,来协调速度。
5.1 SharedFlow 与 StateFlow 对比
| 特性 | SharedFlow | StateFlow |
|---|---|---|
| 初始值 | 不需要 | 必须提供 |
| 值去重 | 无自动去重 | 自动去重(新值==旧值时不发射) |
| 重放机制 | 可配置 (0-N) | 固定重放1(当前状态) |
| 使用场景 | 事件(一次性通知) | 状态(持续存在的值) |
| 值访问 | 无法直接访问 | 通过 .value 获取当前状态 |
| 状态保持 | 不保持状态 | 始终持有当前状态 |
| 典型应用 | 事件总线、通知系统 | UI状态管理、配置管理 |
5.2 结合使用示例(事件 + 状态)
class AuthViewModel : ViewModel() {
// 状态管理
private val _authState = MutableStateFlow<AuthState>(AuthState.Idle)
val authState: StateFlow<AuthState> = _authState.asStateFlow()
// 事件管理
private val _authEvents = MutableSharedFlow<AuthEvent>()
val authEvents: SharedFlow<AuthEvent> = _authEvents.asSharedFlow()
fun login(username: String, password: String) {
viewModelScope.launch {
_authState.value = AuthState.Loading
try {
val user = authRepo.login(username, password)
_authState.value = AuthState.Authenticated(user)
_authEvents.emit(AuthEvent.NavigateToHome)
} catch (e: AuthException) {
_authState.value = AuthState.Error(e)
_authEvents.emit(AuthEvent.ShowErrorDialog(e))
}
}
}
}