协程笔记
1、launch 执行 挂起执行,后面的逻辑不会阻塞
kotlin
CoroutineScope(Dispatchers.Main).launch{
println("1 CoroutineScope.launch ${Thread.currentThread().name} ...")
// 挂起执行,后面的逻辑不会阻塞
launch {
delay(2000)
println("2 CoroutineScope.launch ${Thread.currentThread().name} ... ")
}
println("3 CoroutineScope.launch ${Thread.currentThread().name} ...")
}
2025-08-20 22:13:01.863 30511-30511 System.out com.example.kotlinscope I 1 CoroutineScope.launch main ...
2025-08-20 22:13:01.864 30511-30511 System.out com.example.kotlinscope I 3 CoroutineScope.launch main ...
2025-08-20 22:13:03.871 30511-30511 System.out com.example.kotlinscope I 2 CoroutineScope.launch main ...
2、串行切线程,切换到IO线程直到io线程执行返回后切回当前线程后再继续执行当前线程
kotlin
CoroutineScope(Dispatchers.Main).launch{
println("1 CoroutineScope.launch ${Thread.currentThread().name} ...")
// 串行切线程,切换到IO线程直到io线程执行返回后切回当前线程后再继续执行当前线程
withContext(Dispatchers.IO) {
delay(2000)
println("2 CoroutineScope context ${Thread.currentThread().name} ... ")
}
println("3 CoroutineScope.launch ${Thread.currentThread().name} ...")
}
2025-08-20 22:16:25.385 30728-30728 System.out com.example.kotlinscope I 1 CoroutineScope.launch main ...
2025-08-20 22:16:27.395 30728-30764 System.out com.example.kotlinscope I 2 CoroutineScope context DefaultDispatcher-worker-3 ...
2025-08-20 22:16:27.397 30728-30728 System.out com.example.kotlinscope I 3 CoroutineScope.launch main ...
3、将方法进行抽取 实现串行执行(顺带将withContext 一起抽取)
类似回调里再进行回调方法最终第二个返回进行数据展示 在不同线程间串行
kotlin
CoroutineScope(Dispatchers.Main).launch{
val data = getData()
val processData = processData(data)
println("process data $processData")
}
private suspend fun processData(data: String) = withContext(Dispatchers.Default) {
// 处理数据
"process $data"
}
private suspend fun getData() = withContext(Dispatchers.IO) {
// 网络代码
"data"
}
串行方式2
kotlin
private suspend fun func1():String {
delay(2000)
return "func1"
}
private suspend fun func2():String {
delay(1000)
return "func2"
}
// 串行执行,同一个线程串行
CoroutineScope(Dispatchers.Main).launch{
val f1 = func1()
val f2 = func2()
println(f1 + f2)
}
4、概念,挂起函数为什么不卡线程
// 使用 launch 的 Dispatchers.Default 其实底层是 维护了一个线程池来把代码放在线程池进行运行
// 使用 launch 的 Dispatchers.Main 能切到主线程是因为底层调用了handler.post 方法
5、协程的异步执行
方式1
kotlin
// async 启动的协程会有一个返回对象
val deferred = lifecycleScope.async {
github.contributors("square","okhttp")
}
// 异步执行,最后进行数据合并显示
lifecycleScope.launch {
val res1 = github.contributors("square","retrofit")
val res2 = deferred.await()
showContributors(res1 + res2)
}
方式2
kotlin
val deferred1 = lifecycleScope.async {
github.contributors("square","okhttp")
}
val deferred2 = lifecycleScope.async {
github.contributors("square","okhttp")
}
// 异步执行,最后进行数据合并显示
lifecycleScope.launch {
val res1 = deferred1.await()
val res2 = deferred2.await()
showContributors(res1 + res2)
}
方式3:
kotlin
// 异步执行,最后进行数据合并显示
lifecycleScope.launch {
val deferred1 = async { github.contributors("square","okhttp") }
val deferred2 = async { github.contributors("square","okhttp") }
showContributors(deferred1.await() + deferred2.await())
}
// 异步执行,最后进行数据合并显示,如下写法更优,结构化并发的附加操作
lifecycleScope.launch {
coroutineScope {
val deferred1 = async { github.contributors("square","okhttp") }
val deferred2 = async { github.contributors("square","okhttp") }
showContributors(deferred1.await() + deferred2.await())
}
}
6、结构化并发
取消协程
kotlin
val job = lifecycleScope.launch {
println("start....")
delay(1000)
println("end....")
}
job.cancel()
第二行end...不会打印,一般在页面退出的时候取消当前协程。
kotlin
override fun onDestroy() {
super.onDestroy()
job?.cancel()
}
job取消自己的job
onDestory 中 lifecycleScope 会自动注册cancel 取消所有子孙 启动的协程
launch 外面的管理里面的 launch ,有父子关系
kotlin
lifecycleScope.launch {
launch {
}
}
7、协程join 等待,和async 的区别是,async是有返回值的等待,而join则是没有返回值的等待,使用场景不同
kotlin
lifecycleScope.launch {
val initJob = launch {
init()
}
initJob.join() // 等待初始化完成后后面的才能继续执行,流程上没有返回使用launch join,如果有返回依赖请使用async await
processData()
}
8、让回调格式的函数装在挂起函数里
kotlin
// 让回调格式的函数装在挂起函数里,使用 it.resume(response.body()!!) 进行返回,返回值val 进行接收
// 也可以对其进行抽取使用
lifecycleScope.launch {
val contributors: List<Contributor> = suspendCoroutine {
gitHub.contributorsCall("square", "retrofit")
.enqueue(object : Callback<List<Contributor>> {
override fun onResponse(
call: Call<List<Contributor>>,
response: Response<List<Contributor>>
) {
it.resume(response.body()!!)
}
override fun onFailure(call: Call<List<Contributor>>, t: Throwable) {
it.resumeWithException(t) //抛出这个异常并结束协程,外部使用try catch 进行捕获
}
})
}
}
9、支持取消的协程 suspendCancellableCoroutine
协程的取消需要协程和内部函数一起配合:例如
kotlin
val job = lifecycleScope.launch {
println("Test cancel 1")
delay(500)
println("Test cancel 2")
}
lifecycleScope.launch {
delay(200)
job.cancel()
}
2025-08-20 23:16:23.113 12694-12694 System.out com.example.kotlinscope I Test cancel 1
取消了。
但是如果函数内部不配合,无法取消
kotlin
val job = lifecycleScope.launch {
println("Test cancel 1")
Thread.sleep(500) // 不配合协程的状态机
println("Test cancel 2")
}
lifecycleScope.launch {
delay(200)
job.cancel()
}
2025-08-20 23:17:20.373 13402-13402 System.out com.example.kotlinscope I Test cancel 1
2025-08-20 23:17:20.873 13402-13402 System.out com.example.kotlinscope I Test cancel 2
基于以上如果协程中存在不配合的代码(java 相关的代码的话)则使用 suspendCancellableCoroutine,外部就可以进行取消了 。
如果使用的 suspendCoroutine 则 job.cancel 无法取消内部的代码执行
kotlin
private suspend fun callCancelableSuspend() {
val contributors: List<Contributor> = suspendCancellableCoroutine {
it.invokeOnCancellation {
// 执行取消之后的收尾工作
}
gitHub.contributorsCall(owner = "square", repo = "retrofit")
.enqueue(object : Callback<List<Contributor>> {
override fun onResponse(
call: Call<List<Contributor>>,
response: Response<List<Contributor>>
) {
it.resume(response.body()!!)
}
override fun onFailure(call: Call<List<Contributor>>, t: Throwable) {
it.resumeWithException(t) //抛出这个异常并结束协程,外部使用try catch 进行捕获
}
})
}
}
job = lifecycleScope.launch {
try {
val res = callCancelableSuspend()
showResult(res)
} catch (e: Exception) {
}
}
lifecycleScope.launch {
delay(200)
job?.cancel()
}
10、runBlocking介绍
runBlocking
会创建一个新的协程作用域,并且会阻塞当前线程,直到协程作用域内的所有协程都执行完毕 。它允许在一个传统的阻塞式代码环境中(比如在 main
函数中)启动并等待协程执行完成,常用于测试代码、简单的示例代码以及一些非 Android 应用(如命令行工具等)的入口点,方便在不涉及复杂异步回调的情况下体验协程功能。
kotlin
// runBlocking 包住 整个main 提供一个协程启动的环境,
// 也可以使用 suspend fun main() = coroutineScope {},但是和 runBlocking 没关系了
// 使用场景 测试一个函数返回
fun main() = runBlocking<Unit> {
val contributors = gitHub.contributors(owner = "square", repo = "retrofit")
launch {
// 此处可添加处理贡献者数据的逻辑
}
}
// 不需要 coroutineScope 上下文 ,不需要被取消
// 会阻塞线程 ,直到执行完成后才会返回主线程
// 使用:把协程代码转换成 阻塞式的,给线程使用
runBlocking {
}

11、协程异常处理
kotlin
val handler = CoroutineExceptionHandler { _, exception ->
println("捕获到异常: ${exception.message}")
}
lifecycleScope.launch (handler) {
launch {
throw RuntimeException("第一个协程的异常")
}
launch {
println("捕获到异常继续执行吗?") // 使用 lifecycleScope.launch 不会执行这里,但是如果用 runBlocking 因为第一个异常结束 launch
// 这里故意制造一个数组越界异常
val arr = IntArray(0)
println(arr[1])
}
}
2025-08-21 00:07:38.016 25641-25641 System.out com.example.kotlinscope I 捕获到异常: 第一个协程的异常
kotlin
val handlerOuter = CoroutineExceptionHandler { _, exception ->
println("out捕获到异常: ${exception.message}")
}
val handlerInner = CoroutineExceptionHandler { _, exception ->
println("in捕获到异常: ${exception.message}")
}
lifecycleScope.launch (handlerOuter) {
launch (handlerInner) {
throw RuntimeException("第一个协程的异常")
}
launch {
println("捕获到异常继续执行吗????")
// 这里故意制造一个数组越界异常
val arr = IntArray(0)
println(arr[1])
}
}
不会执行第二个异常。但如果是runBlocking 会走完整个流程,每一个异常都上报(未验证)
2025-08-21 00:09:05.909 26068-26068 System.out com.example.kotlinscope I out捕获到异常: 第一个协程的异常