文章目录
- 前言
- 一. gradle配置
- 二. 创建协程的三种方式
- 2.1 runBlocking
- 2.2 GlobalScope
- 2.3 CoroutineScope
- 三. 总结
前言
协程:英文coroutine,协程可以认为是轻量级的线程,是一套基于Java线程池的封装。相对于线程要处理各种同步问题,协程则可以将其简化,以同步的方式写异步代码。
一. gradle配置
对于Android项目,最新版本的kotlin,只需要在项目下的build.gradle
加上以下几个支持就可以了:
// kotlin核心扩展包,包括协程
implementation androidx.core:core-ktx:1.6.0
// 提供了lifecyclerScope
implementation androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1
// 提供了viewModelScope
implementation androidx.lifecycle:lifecycle-runtime-ktx:2.5.1
对于单独的Kotlin项目,需要以下配置:
// kotlin标准库
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.7.20"
// 协程核心库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1"
二. 创建协程的三种方式
2.1 runBlocking
runBlocking {
// Empty Code
}
runBlocking
顾名思义,该协程创建方式会导致线程的阻塞。
Thread{
runBlocking {
println("runBlocking start!")
delay(2 * 1000)
}
println("Thread end!")
}.start()
输出结果:
runBlocking start!
// 阻塞一秒
Thread end!
执行println("Thread end!")
之前要挂起两秒,delay
函数是一个挂起函数,类似于Java中的sleep
函数(注意:本质上不是同一个东西)。当runBlocking
被阻塞的时候整个线程就会被阻塞掉,正因为如此,runBlocking
是不会用于Android业务开发。
runBlocking {
println("runBlocking!")
delay(1000)
launch { // 创建一个子协程
println("launch!")
}
}
输出结果:
runBlocking!
// 阻塞一秒
launch!
runBlocking
使用挂起函数同样也会阻塞子协程,并且会等到runBlocking
内部所有子协程运行完,才会执行下一步操作
2.2 GlobalScope
GlobalScope
的使用示例:
@OptIn(DelicateCoroutinesApi::class) // 注解压制
fun main(){
GlobalScope.launch {
// Empty Code
}
}
GlobalScope
和runBlocking
的区别是它不会阻塞线程。
@OptIn(DelicateCoroutinesApi::class)
fun main(){
Thread{
GlobalScope.launch {
println("GlobalScope launch!")
delay(1000)
}
println("Thread end !")
}.start()
}
输出结果:
Thread end !
从输出结果可以看出GlobalScope
是不会阻塞线程的。
虽然GlobalScope
不会阻塞线程,但是在Android 开发中同样也不建议使用!因为,它是全局的,并且会一直保留到应用程序死掉为止。
关于GlobalScope
的生命周期可以写一个Android Demo来测试下,首先创建两个Activity
:MainActivity
和TestActivity
,从MainActivity
跳转到TestActivity
,在TestActivity
启动GlobalScope
。TestActivity
的启动代码如下:
R.id.start_btn -> {
GlobalScope.launch(Dispatchers.IO) {
while (true){
delay(1000)
Log.e("test", "GlobalScope!")
}
}
}
通过点击Button启动GlobalScope
,再关闭TestActivity
,你会发现GlobalScope
协程依然在执行!如果你将其换成lifecycleScope
,协程是会被关闭。
那么对于GlobalScope
是否可以用cancel
去关闭呢?事实上也是不行的,当你尝试使用GlobalScope.cancel()
来关闭的时候会抛出一个异常:java.lang.IllegalStateException: Scope cannot be cancelled because it does not have a job: kotlinx.coroutines.GlobalScope@274748c
。
为啥调用GlobalScope.cancel()
会抛出异常?我们可以从代码中找到答案。
// GlobalScope,它是一个继承CoroutineScope的单例
@DelicateCoroutinesApi
public object GlobalScope : CoroutineScope {
/**
* Returns [EmptyCoroutineContext].
*/
override val coroutineContext: CoroutineContext
get() = EmptyCoroutineContext
}
// cancle代码
public fun CoroutineScope.cancel(cause: CancellationException? = null) {
val job = coroutineContext[Job] ?: error("Scope cannot be cancelled because it does not have a job: $this")
job.cancel(cause)
}
GlobalScope
的coroutineContext
是一个EmptyCoroutineContext
。当调用cancel
的时候,coroutineContext[Job]
的取值为null,那么必然直接抛出一个异常。
虽然无法用GlobalScope.cancel()
的方式关闭协程,但是可以用以下方式关闭协程
private var jobInGlobal: Job? = null
// 省略代码...
R.id.start_btn -> {
jobInGlobal = GlobalScope.launch(Dispatchers.IO) {
while (true){
delay(1000)
Log.e("test", "GlobalScope!")
}
}
}
R.id.stop_btn -> {
jobInGlobal?.cancel()
}
可是如果这样做了,可能导致GlobalScope
创建的协程莫名其妙的停止运行!(GlobalScope
协程是全局的),所以需要小心谨慎的对待GlobalScope
。
2.3 CoroutineScope
CoroutineScope
是比较推荐的使用方法,可以通过 context 参数去管理和控制协程的生命周期,也可以通过cancel
来关闭协程,还有就是CoroutineScope
和GlobalScope
一样也不会阻塞线程。CoroutineScope
创建示例:
CoroutineScope(Dispatchers.IO).launch {
// Empty Code
}
CoroutineScope
在Android中的使用可以说明不会阻塞线程:
button.setOnClickListener { view ->
when(view.id){
R.id.test_button->{
CoroutineScope(Dispatchers.IO).launch { // 运行于IO线程上
Log.e("scboy", "CoroutineScope start !")
delay(1000)
Log.e("scboy", "CoroutineScope end !")
}
Log.e("scboy", "CoroutineScope click !")
}
}
}
输出结果:
18:12:05.956 E/scboy: CoroutineScope click !
18:12:05.956 E/scboy: CoroutineScope start !
18:12:06.962 E/scboy: CoroutineScope end !
CoroutineScope click !
和CoroutineScope start !
先执行了,CoroutineScope end !
等待一秒后再执行,以此可以看出CoroutineScope
没有阻塞UI线程。
和GlobalScope
一样,通过cancel
方法结束协程:
private var job: Job? = null
// 省略代码...
R.id.start_btn -> {
job = CoroutineScope(Dispatchers.IO).launch {
while (true){
delay(1000)
Log.e("test", "CoroutineScope!")
}
}
}
R.id.stop_btn -> {
job?.cancel()
}
三. 总结
对于三种创建协程的方式runBlocking
,CoroutineScope
,GlobalScope
,在实际开发中使用CoroutineScope
来创建协程。