首页 Android正文

Kotlin协程的详解

yuange Android 2020-12-28 862 0 Kotlin协程

什么是协程

  • 轻量级线程,

  • 用户态的,

  • 很像线程但又不是线程
    其实上述所说的概念或者特点,可能所属于其他语言中协程的,但是在Kotlin中关于协程是怎么样的呢?

Kotlin中的协程

协程是Kotlin官方提供的线程API,更准确的说是一个线程框架
类似于Java中的Executors 以及Android中的Handler和AsyncTask 以及NetFlex的RxJava
注意这里所说的协程是kotlin中的协程!!!下面所说的协程也就是Kotlin中的协程!

为什么kotlin中要提供协程呢

好处:方便,借助于Kotlin的语言优势,它比Java中基于线程的方案会更方便更好用!
最重要的是看起来同步的方式写出异步的代码

...val product = apiService.getProduct() //发起网络请求(子线程中)priceTv.text = product.price //更新UI(UI线程中)....
怎么用

协程最基本的功能就是并发也就是多线程,我们可以把任务切到到后台执行也可以是前台
在Kotlin的协程中我们可以把多个不同的任务执行写到一个代码块里

launch(Dispatchers.main){ // UI线程
    // 发起网络请求(子线程中)任务执行切到子线程中,当任务结束之后在切换到UI线程中
    // 这个Java应该是做不到的
    val product = apiService.getProduct() 
    priceTv.text = product.price //更新UI(UI线程中)}

Kotlin中的协程帮助我们改变并发任务的操作难度
比如我们业务场景中一个控件上的数据需要依赖两个接口,我们之前的做法可能是
在接口1的会调中拿到结果1后再去请求接口2中的结果2 之后合并接口的结果
可能RxJava用的好,会采用zip操作符
但是在协程中我们可以这样写,怎么写呢 就是看似用同步的方式写出异步的代码

launch(Dispatchers.main){ // UI线程
    // 发起网络请求(子线程中)任务执行切到子线程中,当任务结束之后在切换到UI线程中
    // 这个Java应该是做不到的
    val result1 = async{apiService.getResult1()} 
    val result2 = async{apiService.getResult2()}
    val realResult = handleResult(result1,result2)}

是不是变得很简单呢!!

launch()

上文中的launch函数就是用来创建协程的,而后面的参数就是指定了创建的协程运行在哪个线程中
我们创建的协程到底是啥呢,其实就是创建的代码块中执行的代码就是所谓的协程

val product = apiService.getProduct() priceTv.text = product.price //更新UI(UI线程中)

所以说既然知道了怎么创建协程,那什么时候创建协程呢。那就是需要线程的切换的时候,比如我们需要子线程拿到数据,UI线程中更新数据到控件上,就可以创建
协程来完成。

launch(Dispatchers.main){
   val data = withContext(Dispatchers.IO){
        apiService.getData()
   }
   priceTv.text = data.price}

上文中的withContext()函数起到了两个作用

  • 创建了一个协程,指明这个协程运行在什么线程中

  • 当这个协程执行完毕之后 会重新切换到UI线程中,来继续执行

上文中我们可以将withContext放入到一个功能函数中

launch(Dispatchers.main){
   val data = getData()
   priceTv.text = data.price}suspend fun getData():Data{
  return withContext(Dispatchers.IO){
        apiService.getData()
   }}

但是注意了你包裹withContext函数的函数必须使用suspend修饰符来修饰,这是为啥呢?
因为 withContext函数也是suspend关键字修饰,而suspend修饰符修饰的函数必须在协程中调用或者另外一个suspend修饰的函数中调用

public suspend fun <T> withContext(
    context: CoroutineContext,
    block: suspend CoroutineScope.() -> T): T = suspendCoroutineUninterceptedOrReturn sc@ {}

suspend修饰符修饰的函数在Kotlin中又称为挂起函数,这个挂起是非阻塞式的;
比如在主线程中执行到了一个挂起函数,执行到它的时候协程就会挂起,但是这个挂起并不会影响你主线程
所以说这个挂起就是挂起这个协程:明白点说就是在当前线程中,当代码执行到协程的代码时候 协程就会从当前线程中脱离,记住是脱离(不会影响到当前线程的,并且协程中的代码此时会在协程指定的线程中执行
和当前的线程没有一点关系)
当协程中的代码执行完毕,它会自动切换到协程所在的线程,记住是自动的哟!
上文中所说suspend修饰符修饰的函数必须在协程中调用或者另外一个suspend修饰的函数中调用,为啥呢?因为挂起和自动切换都是协程的功能,
所以说挂起函数需要放在协程中调用,主要是为了 1 能被挂起(切到协程所指定的线程中);2 代码执行完毕能自动切换到之前线程中。

那么suspend关键字的作用到底是什么呢?是起到一个提醒的作用
就是说告诉调用者 我是一个耗时操作的函数,你需要在协程或者另外一个挂起函数中使用我
这个提醒有什么作用呢?主线程不卡,你声明了这个函数是挂起函数就说明这个函数会做一些耗时操作,你都使用协程开启了一个线程去执行,就不会影响到主线程中

怎么自定义一个挂起函数呢?
什么时候挂起
  • 耗时的操作(I/O,cpu计算)

  • 在这个耗时的操作有一个特殊的情况就是-等待

怎么写

launch{}
sync{}

协程非阻塞式的挂起

其实所谓的挂起就是 不卡当前线程的操作,因为挂起函数执行在协程指定的线程中,所以从当前线程切换到这个线程,肯定不卡当前线程啦!
协程的非阻塞式挂起是用阻塞的方式写出了非阻塞式的代码而已,因为无论你是Kotlin的挂起去执行任务还是Java中的开启线程执行任务,
本质上都是开启一个线程去承担这个耗时任务而已。

两种创建协程的真正方式

其实你在正常的Kotlin代码中直接写launch()是调不到的,那应该怎么写呢

顶层协程
GlobalScope.launch(Dispatchers.main){
   val data = getData()
   priceTv.text = data.price}suspend fun getData():Data{
  return withContext(Dispatchers.IO){
        apiService.getData()
   }}

其实嘛,launch函数是我们CoroutineScope的一个扩展函数,我们的GlobalScope是实现了这个接口调用了这个扩展函数然后创建了协程
这种方式创建的协程在程序运行完毕的时候也会跟着一起结束的!

测试协程(我自己起的)
runBlocking {
        launch {
            println("launch1")
            delay(1000)
            println("launch1 finished")

        }
        launch {

            println("launch2")
            delay(1000)
            println("launch2 finished")

        }
    }

是的这种使用runBlocking方式也能创建协程,相比较于GlobalScope它会保证协程内的任务执行完毕之后在保证程序结束,
所以这种方式我们可以在测试的时候用一用,线上就不要这么搞了,容易出现问题的!


所以总结说一下:
协程是什么:协程即使切线程
挂起是什么:挂起就是既能切线程又能自动切回来的
协程的非阻塞式是什么:以阻塞式的方式写出了非塞式的代码而已

评论

在线客服-可直接交谈

您好!有什么需要可以为您服务吗?