Kotlin协程学习之路(一):初识协程

前言

协程:英文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的使用示例:

扫描二维码关注公众号,回复: 14990751 查看本文章
		@OptIn(DelicateCoroutinesApi::class) // 注解压制
        fun main(){
    
    
            GlobalScope.launch {
    
    
				// Empty Code
            }
        }

GlobalScoperunBlocking的区别是它不会阻塞线程。

		@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来测试下,首先创建两个ActivityMainActivityTestActivity,从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)
}

GlobalScopecoroutineContext是一个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来关闭协程,还有就是CoroutineScopeGlobalScope一样也不会阻塞线程。
CoroutineScope创建示例:

CoroutineScope(Dispatchers.IO).launch {
    
    
	// Empty Code
}

通过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()
            }

三. 总结

对于三种创建协程的方式runBlockingCoroutineScopeGlobalScope,在实际开发中使用CoroutineScope来创建协程。

猜你喜欢

转载自blog.csdn.net/RQ997832/article/details/128222584