kotlin 协程笔记
协程笔记
1、launch 执行 挂起执行,后面的逻辑不会阻塞
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线程执行返回后切回当前线程后再继续执行当前线程
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 一起抽取)
类似回调里再进行回调方法最终第二个返回进行数据展示 在不同线程间串行
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
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
// 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
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:
// 异步执行,最后进行数据合并显示
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、结构化并发
取消协程
val job = lifecycleScope.launch {println("start....")delay(1000)println("end....")
}
job.cancel()
第二行end…不会打印,一般在页面退出的时候取消当前协程。
override fun onDestroy() {super.onDestroy()job?.cancel()
}
job取消自己的job
onDestory 中 lifecycleScope 会自动注册cancel 取消所有子孙 启动的协程
launch 外面的管理里面的 launch ,有父子关系
lifecycleScope.launch { launch { }
}
7、协程join 等待,和async 的区别是,async是有返回值的等待,而join则是没有返回值的等待,使用场景不同
lifecycleScope.launch {val initJob = launch {init()}initJob.join() // 等待初始化完成后后面的才能继续执行,流程上没有返回使用launch join,如果有返回依赖请使用async await processData()
}
8、让回调格式的函数装在挂起函数里
// 让回调格式的函数装在挂起函数里,使用 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
协程的取消需要协程和内部函数一起配合:例如
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
取消了。
但是如果函数内部不配合,无法取消
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 无法取消内部的代码执行
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 应用(如命令行工具等)的入口点,方便在不涉及复杂异步回调的情况下体验协程功能。
// runBlocking 包住 整个main 提供一个协程启动的环境,
// 也可以使用 suspend fun main() = coroutineScope {},但是和 runBlocking 没关系了
// 使用场景 测试一个函数返回
fun main() = runBlocking<Unit> {val contributors = gitHub.contributors(owner = "square", repo = "retrofit")launch {// 此处可添加处理贡献者数据的逻辑}
}// 不需要 coroutineScope 上下文 ,不需要被取消
// 会阻塞线程 ,直到执行完成后才会返回主线程
// 使用:把协程代码转换成 阻塞式的,给线程使用
runBlocking {}
11、协程异常处理
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 捕获到异常: 第一个协程的异常
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捕获到异常: 第一个协程的异常