协程调度器
协程调度器决定协程执行的线程,常见调度器有:
-
Dispatchers.Default:后台线程池,适合 CPU 密集型任务。
-
Dispatchers.IO:优化的线程池,适合 I/O 操作。
-
Dispatchers.Main:主线程,适合 UI 操作。
使用示例
val scope = CoroutineScope(Dispatchers.IO) scope.launch { // 执行网络请求等 I/O 操作 }
协程作用域
协程作用域是协程执行的上下文环境,它定义了协程的生命周期,当作用域被取消时,其内部的协程也会被取消。常见的协程作用域有以下几种:
-
GlobalScope:全局作用域,生命周期与应用相同,不自动管理,易内存泄漏。
-
lifecycleScope:绑定
LifecycleOwner(Activity,Fragment)
,生命周期同步,自动取消内部协程。 -
viewModelScope:绑定
ViewModel
,生命周期同步,自动取消内部协程。 -
CoroutineScope:自定义作用域,手动管理生命周期,需显式取消。
使用示例:
val scope = CoroutineScope(Dispatchers.Default) scope.launch { // 协程代码 delay(2000) println("协程执行完毕") } // 取消作用域, 开启的协程也会取消, "协程执行完毕"不会打印 scope.cancel()
协程
开启协程
通过协程作用域,可以使用 launch
和 async
构建器来创建协程。
-
launch
launch
启动协程,返回Job
,无返回值。class MyActivity : AppCompatActivity() { fun startCoroutine() { val job1 = lifecycleScope.launch { delay(2000) println("协程1执行完毕") } val job2 = lifecycleScope.launch { job1.join() // 等待协程1执行完毕但没有返回值 println("协程2执行完毕") } } }
-
async
async
启动协程,返回Deferred
,用于获取结果。class MyViewModel : ViewModel() { fun startCoroutine() { val deferred1 = viewModelScope.async { delay(2000) 42 // 返回值 } val deferred2 = viewModelScope.async { val result = deferred1.await() // 等待协程1执行完成并获取结果 println("协程1执行结果:$result") 42 // 返回值 } } }
先打印“主线程继续执行”,然后等待协程执行完成,获取到返回值 42 后打印“协程执行结果:42”。
取消协程
-
cancel
val scope = CoroutineScope(Dispatchers.Default) val job = scope.launch { // 协程代码 } job.cancel() // 取消协程
子协程
作用域
在协程作用域内启动的协程称为子协程,子协程的生命周期受到父协程(即子协程作用域)的管理。
当父协程被取消时,其内部的所有子协程也会被取消。例如:
val scope = CoroutineScope(Dispatchers.Default)
val parentJob = scope.launch {
// 开启子协程 1
val childJob1 = launch {
delay(2000)
println("子协程 1 执行完毕")
}
// 开启子协程 2
val childJob2 = launch {
delay(3000)
println("子协程 2 执行完毕")
}
cancel() // 取消父协程
}
当父协程执行到 cancel()
时,子协程 1 和子协程 2 也会被取消,不会继续执行。
挂起函数
挂起函数可以在执行过程中挂起,不会阻塞线程,当条件满足时恢复执行。挂起函数需要用 suspend
关键字修饰。
定义
-
定义挂起函数
suspend fun fetchData(): String { delay(1000) // 模拟耗时的网络请求 return "数据" }
-
在协程中调用挂起函数
只能在协程或其他挂起函数中调用挂起函数。例如:
val scope = CoroutineScope(Dispatchers.Default) scope.launch { val data = fetchData() println("获取到的数据:$data") }
调用
fetchData
时会挂起协程,延迟一秒后获取到数据并打印。
withContext
-
基本用法
withContext
在协程中切换调度器执行代码,完成后自动恢复原调度器,适用于不同线程操作。suspend fun fetchData(): String = withContext(Dispatchers.IO) { delay(1000) // 模拟网络请求的延迟 "获取到的数据" }
调用
fetchData
时,先在 IO 线程请求数据,完成后自动返回原线程。
withTimeoutOrNull
-
基本用法
使用
withTimeout
或withTimeoutOrNull
可以设置协程超时时间,例如:val scope = CoroutineScope(Dispatchers.Default) scope.launch { // 协程代码如果在 1000 毫秒内没有执行完成,会返回 null val result = withTimeoutOrNull(1000) { delay(2000) "结果" } if (result != null) { println("协程执行结果:$result") } else { println("协程超时") } }
awaitAll
如果有多个 Deferred
对象,可以使用 awaitAll
方法等待它们全部完成。例如:
GlobalScope.launch {
val deferred1 = async { delay(1000); "结果 1" }
val deferred2 = async { delay(1500); "结果 2" }
val results = awaitAll(deferred1, deferred2)
println("所有结果:${results.joinToString()}")
}
这里 awaitAll
会等待 deferred1
和 deferred2
两个协程都执行完成,并返回一个包含所有结果的列表。
supervisorScope
-
基本用法
supervisorScope
让子协程独立执行,子协程失败不影响父协程。val scope = CoroutineScope(Dispatchers.Default) scope.launch { supervisorScope { val child1 = launch { // 子协程 1 delay(1000) throw Exception("子协程 1 异常") } val child2 = launch { // 子协程 2 delay(2000) println("子协程 2 执行完毕") } child1.join() child2.join() } println("父协程执行完毕") }
子协程 1 异常不影响子协程 2 和父协程的执行。
Flow
Flow 是一个用于处理异步数据流的协程构建器,它提供了丰富的操作符来处理和转换数据流。在工作开发中Flow也是协程使用最广泛的方式。
创建 Flow
使用 flow
构建器
使用 flow
构建器可以创建一个自定义的 Flow。在 flow
块中,可以使用 emit
函数发送数据。
fun simpleFlow(): Flow<Int> = flow {
for (i in 1..3) {
delay(1000) // 模拟数据生成的延迟
emit(i) // 发送数据
}
}
使用 asFlow
扩展函数
可以将集合或数组转换为 Flow。
val numbers = listOf(1, 2, 3, 4, 5)
val numbersFlow = numbers.asFlow()
收集 Flow
使用 collect
方法可以收集 Flow 中的数据。collect
是一个挂起函数,必须在协程作用域内调用。
GlobalScope.launch {
simpleFlow().collect { value ->
println("接收到的数据:$value")
}
}
数据转换
使用 map
操作符
可以使用 map
操作符对 Flow 中的每个数据项进行转换。
simpleFlow()
.map { it * 2 } // 将每个值乘以 2
.collect { value ->
println("转换后的数据:$value")
}
使用 filter
操作符
可以使用 filter
操作符过滤 Flow 中的数据。
simpleFlow()
.filter { it > 2 } // 过滤出大于 2 的值
.collect { value ->
println("过滤后的数据:$value")
}
异常处理
使用 catch
操作符
可以使用 catch
操作符捕获和处理 Flow 中的异常。
simpleFlow()
.catch { e ->
println("捕获到异常:${e.message}")
emit(0) // 发送一个默认值
}
.collect { value ->
println("接收到的数据:$value")
}
合并多个 Flow
使用 combine
操作符
可以使用 combine
操作符合并多个 Flow,并对它们的最新值进行处理。
val flow1 = flowOf(1, 2, 3)
val flow2 = flowOf("A", "B", "C", "D")
flow1.combine(flow2) { a, b ->
"$a$b"
}.collect { value ->
println("合并后的数据:$value")
}
// 打印
1 -> A
2 -> B
3 -> C
使用 zip
操作符
可以使用 zip
操作符将两个 Flow 中的值配对。
val flow1 = flowOf(1, 2, 3)
val flow2 = flowOf("A", "B", "C", "D")
flow1.zip(flow2) { a, b ->
"$a$b"
}.collect { value ->
println("配对后的数据:$value")
}
// 打印
1 -> A
2 -> B
3 -> C
3 -> D
终止操作
使用 take
操作符
可以使用 take
操作符限制 Flow 中的数据项数量。
simpleFlow()
.take(2) // 只取前两个数据项
.collect { value ->
println("接收到的数据:$value")
}
使用 first
操作符
可以使用 first
操作符获取 Flow 中的第一个数据项。
GlobalScope.launch {
val firstValue = simpleFlow().first()
println("第一个数据项:$firstValue")
}
背压处理
背压(Back pressure)是指当数据的生产速度超过消费速度时,消费者对生产者产生的一种反馈压力。这种情况下,如果不加以处理,可能会导致数据堆积、内存溢出,甚至系统崩溃。
使用 buffer
操作符
通过缓冲区降低背压,保留所有数据,如果缓冲区满了,生产者会被暂停,直到消费者从缓冲区中取出数据。适用于需要减少背压影响,同时保留所有数据的场景。
simpleFlow()
.buffer()
.collect { value ->
println("接收到的数据:$value")
delay(2000) // 模拟耗时处理
}
使用 conflate
操作符
conflate
实际上是 buffer
的一个特例,等同于 buffer(capacity = Channel.CONFLATED)
。它不会保留所有数据,而是只保留最新的数据。这种方式可以显著减少处理频率,适合对最新数据更敏感的场景。
simpleFlow()
.conflate()
.collect { value ->
println("接收到的数据:$value")
delay(2000) // 模拟耗时处理
}
与 LiveData 集成
可以将 Flow 转换为 LiveData,以便在 ViewModel 中使用。
class MyViewModel : ViewModel() {
val usersLiveData: LiveData<List<User>> = flow {
// 模拟从网络或数据库获取数据
delay(2000)
emit(listOf(User(1, "Alice", 30), User(2, "Bob", 25)))
}.asLiveData(viewModelScope.coroutineContext)
}
源码分析
这里是用的装饰器模式,每一步操作符操作都是封装一个新的 Flow
对上一个 Flow
进行扩展,调用上一个 Flow
的 collect
方法传入 FlowCollector
数据收集器拿到数据流的 值,然后做相应的操作处理后,再调用 collector
的 emit
方法将值传出去,下一个操作符还是同样的操作。一直到最后一个操作符调用才会真正执行数据流下发的逻辑。
用java代码比较直观
public class MapFlow {
private Flow preFlow;
private MapFuction mapFun;
public void collect(FlowCollector collector) {
// 调用上一个Flow的collect方法,拿到数据流的值
preFlow.collect(new FlowCollector() {
public void emit(String s) {
// map转换后将值传出去
collector.emit(mapFun(s));
}
});
}
}
高级用法
produce
-
基本用法
produce
可创建协程生产者,向 Channel 发送数据,其他协程可从 Channel 接收。val channel = GlobalScope.produce<Int>(capacity = Channel.UNLIMITED) { for (i in 1..5) { delay(1000) // 模拟数据生成的延迟 send(i) // 将数据发送到 Channel } } GlobalScope.launch { for (value in channel) { println("接收到的数据:$value") } channel.cancel() // 取消生产者协程 }
这里
produce
创建了一个生产者协程,每隔一秒生成一个数字并发送到Channel
中。另一个协程通过for
循环从Channel
中接收数据并打印。