1、什么是协程:
协程的理解和线程有点类似,可以简单的理解为轻量级的线程。线程需要依靠操作系统的调度才能实现不同线程之间的转换,而协程却可以的编程语言的层面实现不同协程之间的转换。协程允许我们在单线程模式下模拟多线程的编程效果,代码的挂起和恢复都是由编程语言自行控制。
developers:
协程是一种并发设计模式,您可以在 Android 平台上使用它来简化异步执行的代码。协程是在版本 1.3 中添加到 Kotlin 的,它基于来自其他语言的既定概念。
2、功能:(来自developers)
与其他异步解决方案相比,协程具备诸多优势,其中包括:
- 轻量:您可以在单个线程上运行多个协程,因为协程支持暂停,不会使正在运行协程的线程阻塞。暂停比阻塞节省内存,且支持多个并行操作。
- 内存泄露更少:使用结构化并发机制在一个作用域内执行多个操作。
- 内置取消支持:取消功能会自动通过正在运行的协程层次结构传播。
- Jetpack 集成:许多 Jetpack 库都包含提供全面协程支持的扩展。某些库还提供自己的协程作用域,可供您用于结构化并发。
3、基本用法:
首先添加依赖:
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
首先我们在函数中直接调用:
fun main() {
GlobalScope.launch {
println("codes has start")
}
}
运行后发现没有输出,这是因为Global.lunch函数每次都是一个顶层协程,当程序运行结束时会跟着一起结束,这样我们在主程序中阻塞一秒:
fun main() {
GlobalScope.launch {
println("codes has start")
}
Thread.sleep(1000)
}
这样便得到了以下结果
但是,如果代码块中1秒执行不完,依然会有问题,因此引出了runBlocking:
fun main() {
runBlocking {
println("codes has start")
}
}
他可以保证协程中的代码全部执行完毕之前一直阻塞当前线程,但是如果是正式环境容易产生性能问题。
随着lunch函数的逻辑越来越复杂,我们会将一部分代码提取到单独的函数中。但是lunch函数编写的代码都是有协程作用域的,但是单独的函数并没有协程作用域,这时需要借助coroutineScope函数来提供协程作用域。而coroutineScope是一个挂起函数,kotlin规定,挂起函数之间是可以相互调用的。
suspend fun pringDot()= coroutineScope {
launch {
println(".")
delay(1000)
}
}
其中:
public suspend fun delay(timeMillis: Long) {
if (timeMillis <= 0) return // don't delay
return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
}
}
/** Returns [Delay] implementation of the given context */
internal val CoroutineContext.delay: Delay get() = get(ContinuationInterceptor) as? Delay ?: DefaultDelay
可以看到,delay函数是一个挂起函数。
需要说明的是coroutineScope和runblocking十分的相似,coroutineScope会将当前协程阻塞,只有当其作用域的代码全部执行完毕后其之后的代码才能执行。但是和runblocking不同的是,它只会阻塞当前协程,不会影响其他协程,也不会影响任何线程。
4、处理长时间任务:
协程在常规函数的基础上增加了两项操作:
-
suspend
用于暂停执行当前协程,并保存所有局部变量。 -
resume
用于让已暂停的协程从暂停处继续执行。
如以下就是一个简单的协程实现;
suspend fun fetchDocs() { // Dispatchers.Main
val result = get("https://developer.android.com") // Dispatchers.IO for `get`
show(result) // Dispatchers.Main
}
suspend fun get(url: String) = withContext(Dispatchers.IO) { /* ... */ }
在上面的示例中,get()
仍在主线程上运行,但它会在启动网络请求之前暂停协程。当网络请求完成时,get
会恢复已暂停的协程,而不是使用回调通知主线程。
协程的调度程序:
- Dispatchers.Main - 使用此调度程序可在 Android 主线程上运行协程。此调度程序只能用于与界面交互和执行快速工作。示例包括调用
suspend
函数、运行 Android 界面框架操作,以及更新 LiveData 对象。 - Dispatchers.IO - 此调度程序经过了专门优化,适合在主线程之外执行磁盘或网络 I/O。示例包括使用 Room 组件、从文件中读取数据或向文件中写入数据,以及运行任何网络操作。
- Dispatchers.Default - 此调度程序经过了专门优化,适合在主线程之外执行占用大量 CPU 资源的工作。用例示例包括对列表排序和解析 JSON。
重要提示:使用 suspend
不会让 Kotlin 在后台线程上运行函数。suspend
函数在主线程上运行是一种正常的现象。在主线程上启动协程的情况也很常见。当您需要确保主线程安全时(例如,从磁盘上读取数据或向磁盘中写入数据、执行网络操作或运行占用大量 CPU 资源的操作时),应始终在 suspend
函数内使用 withContext()
。
5、启动协程
-
launch 可启动新协程而不将结果返回给调用方。任何被视为“一劳永逸”的工作都可以使用
launch
来启动。 -
async会启动一个新的协程,并允许您使用一个名为
await
的暂停函数返回结果。
Job 是协程的句柄。使用 launch
或 async
创建的每个协程都会返回一个 Job
实例,该实例唯一标识协程并管理其生命周期。您还可以将 Job
传递给 CoroutineScope
以进一步管理其生命周期,如以下示例所示:
class ExampleClass {
...
fun exampleMethod() {
// Handle to the coroutine, you can control its lifecycle
val job = scope.launch {
// New coroutine
}
if (...) {
// Cancel the coroutine started above, this doesn't affect the scope
// this coroutine was launched in
job.cancel()
}
}
}
关于withContext()函数,可以理解为async的简化版写法,但是withContext()强制我们指定一个线程参数。