协程知识点

协程作用

1.协程可以同步的方式去编写异步执行的代码

协程可以使用阻塞的方式写出非阻塞式的代码,解决并发中常见的回调地狱

协程在写法上和普通的顺序代码类似,同步的方式去编写异步执行的代码

GlobalScope.launch(Dispatchers.Main) {//开始协程:主线程
    val result = userApi.getUserSuspend("suming")//网络请求(IO 线程)//耗时阻塞的操作交给IO线程
    tv_name.text = result?.name //更新 UI(主线程) // 恢复到主线程
}

协程知识点_第1张图片

2.协程可以降低异步程序的设计复杂度

3.在不同的线程之间切换,保证线程安全。

 一个线程上可以一个或多个协程。

4. 处理耗时任务(比如网络请求、解析JSON数据、从数据库中进行读写操作等)

协程特点

  • 轻量:您可以在单个线程上运行多个协程,因为协程支持挂起,不会使正在运行协程的线程阻塞。挂起比阻塞节省内存,且支持多个并行操作。
  •  内存泄漏更少:使用结构化并发机制在一个作用域内执行多项操作。
  • 内置取消支持:取消操作会自动在运行中的整个协程层次结构内传播。
  • Jetpack 集成:许多 Jetpack 库都包含提供全面协程支持的扩展。某些库还提供自己的协程作用域,可供您用于结构化并发。

Kotlin Coroutine 生态

  kotlin的协程实现分为了两个层次:

基础设施层(协程标准库) 标准库的协程API,主要对协程提供了概念和语义上最基本的支持;
业务框架层 kotlin.coroutines 协程的上层框架支持,基于协程标准库实现的封装,也是我们日常开发使用的协程扩展库。

 协程知识点_第2张图片

协程依赖库

dependencies {
    classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.32"
}

dependencies {
     //协程标准库
    implementation "org.jetbrains.kotlin:kotlin-stdlib:1.4.32"
    //协程核心库
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3"
    //协程Android支持库,提供安卓UI调度器
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3"
}

协程的原理

协程的最核心的原理就是能够把函数或者一段程序能够被挂起,稍后再在挂起的位置恢复

 协程的构建器

  runBlocking:T 一般在项目中不会使用,主要是为main函数和测试设计的。
     launch 创建一个新的协程,不会阻塞当前线程,必须在协程作用域中才可以调用。它返回的是一个该协程任务的引用,即Job对象。这是最常用的用于启动协程的方式。
         1. GlobalScope.launch()  全局的协程 不推荐这种用法
          2.CoroutineScope.launch()  启动一个新的协程而不阻塞当前线程,并返回对协程的引用作为一个Job。
          组件销毁时,协程一并销毁  在应用中最推荐使用的协程使用方式。

     

  async

1.创建一个新的协程,不会阻塞当前线程,必须在协程作用域中才可以调用。并返回Deffer对象,可通过调用Deffer.await()方法等待该子协程执行完成并获取结果。常用于并发执行-同步等待和获取返回值的情况。

  注意:await() 不能在协程之外调用,因为它需要挂起直到计算完成,而且只有协程可以以非阻塞的方式挂起。所以把它放到协程中。

 2. 当在协程作用域中使用async函数时可以创建并发任务:

注意:⑴如果Deferred不执行await()async内部抛出的异常不会被logCattryCatch捕获, 但是依然会导致作用域取消和异常崩溃; 但当执行await时异常信息会重新抛出。

           ⑵惰性并发,如果将async函数中的启动模式设置为CoroutineStart.LAZY懒加载模式时则只有调用Deferred对象的await时(或者执行   async.satrt())才会开始执行异步任务。

 launch 与async的不同

launch构建器:适合执行 "一劳永逸" 的工作,意思就是说它可以启动新协程而不需要结果返回;
async构建器:可启动新协程并允许您使用一个名为await的挂起函数返回result,并且支持并发。
如果使用async作为最外层协程的开启方式,它期望最终是通过调用 await 来获取结果 (或者异常),
所以默认情况下它不会抛出异常。这意味着如果使用 async启动新的最外层协程,而不使用await,它会静默地将异常丢弃。

Job 与Deferred的不同

Job 
 ⑴Job 是门把手 协程是门  通过门把手Job 可以控制门(协程)。 Job是launch构建协程返回的一个协程任务,完成时是没有返回值的。

⑵可以把Job看成协程对象本身。协程的操作方法都在Job身上。 Job具有生命周期并且可以取消,它也是上下文元素,继承自CoroutineContext。

public interface Job : CoroutineContext.Element {
    //活跃的,是否仍在执行
    public val isActive: Boolean

    //启动协程,如果启动了协程,则为true;如果协程已经启动或完成,则为false
    public fun start(): Boolean
    
    //取消Job,可通过传入Exception说明具体原因
    public fun cancel(cause: CancellationException? = null)
    
    //挂起协程直到此Job完成
    public suspend fun join()
    
    //取消任务并等待任务完成,结合了[cancel]和[join]的调用
    public suspend fun Job.cancelAndJoin()

    //给Job设置一个完成通知,当Job执行完成的时候会同步执行这个函数
    public fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle
}

状态 说明
isActive 活跃的。当Job处于活动状态时为true,如果Job已经开始,但还没有完成、也没有取消或者失败,则是处于active状态。
isCompleted 已完成。当Job由于任何原因完成时为true,已取消、已失败和已完成Job都是被视为完成状态。
isCancelled 已退出。当Job由于任何原因被取消时为true,无论是通过显式调用cancel或这因为它已经失败亦或者它的子或父被取消,都是被视为已退出状态。

 

Job几个比较有用的函数:
 ❶  join()是一个挂起函数,它需要等待协程的执行,如果协程尚未完成,join()立即挂起,直到协程完成;如果协程已经完成,join()不会挂起,而是立即返回。
   一个Job可以包含多个子Job,当父Job被取消后,所有的子Job也会被自动取消;
   当子Job被取消或者出现异常后父Job也会被取消。具有多个子 Job 的父Job 会等待所有子Job完成(或者取消)后,自己才会执行完成。
  ❷  cancel(cause: CancellationException? = null)     //取消Job协程,可通过传入Exception说明具体原因  

  ❸ cancelAndJoin()//   取消任务并等待任务完成,结合了[cancel]和[join]的调用  以这个使用为主

 MainScope().launch(context = Dispatchers.Main,block={
            val startTime = System.currentTimeMillis()
            val job = launch(context = Dispatchers.Default, block = {
                var nextPrintTime = startTime
                var i = 0

                while (isActive) { //当子协程job是活跃状态下(isActive = true)才会打印输出,  在job子协程取消后, 非活跃状态(isActive = false) 已取消,第3和第4条数据不会被打印出来。
                    if (System.currentTimeMillis() >= nextPrintTime) {//每秒钟打印两次消息
                        println("job:执行打印  ${i++} ...")  //  job:执行打印  1 ...   job:执行打印  2 ...
                        nextPrintTime += 500
                    }
                }
            })

            delay(1200)//延迟1.2s
            println("等待1.2秒后")
           job.cancelAndJoin()//取消任务并等待任务完成 //挂起并调用协程,直到被取消的job完成
            println("协程被取消并等待完成")  // 执行此行代码

        })

  

 /**
     * job:执行打印  1 ...
     * job:执行打印  2 ...
     *  等待1.2秒后
     *  协程被取消并等待完成
     * */

  Deferred

Deferred继承自Job,具有与Job相同的状态机制。
它是async构建协程返回的一个协程任务,可通过调用await()方法等待协程执行完成并获取结果。
不同的是Job没有结果值,Deffer有结果值。  

public interface Deferred : Job {
    //等待协程执行完成并获取结果
    public suspend fun await(): T
}

  • await(): 等待协程执行完毕并返回结果,如果异常结束则会抛出异常;如果协程尚未完成,则挂起直到协程执行完成。
  • T:    这里多了一个泛型参数T,它表示返回值类型,通过await()函数可以拿到这个返回值。

 Deferred的取消

1.async调用await()返回数据结果

 MainScope().launch(context = Dispatchers.Main,block={
            val asyncDeferred :Deferred = async(context = Dispatchers.IO, block = {
                delay(3000) // 在给定时间内延迟协程而不阻塞线程,并在指定时间后恢复协程。你可以认为它实际上就是触发了一个延时任务,告诉协程调度系统多久之后再来执行后面的代码。
                println("asyncTest")
                return@async
            })
            val result:Unit = asyncDeferred.await()
            println(" 子协程async的执行返回结果值result=$result")

        })

 

/**
 *   asyncTest
 *    子协程async的执行返回结果值result=kotlin.Unit
 *
 * */

2.在deferred.await()之后调用deferred.cancel()不会有任何情况发生,因为协程已经处理结束。

 MainScope().launch(context = Dispatchers.Main,block={
        val asyncDeferred :Deferred = async(context = Dispatchers.IO, block = {
            delay(3000) // 在给定时间内延迟协程而不阻塞线程,并在指定时间后恢复协程。你可以认为它实际上就是触发了一个延时任务,告诉协程调度系统多久之后再来执行后面的代码。
            println("asyncTest")
            return@async
        })
        val result:Unit = asyncDeferred.await()
        println(" 子协程async的执行返回结果值result=$result")
        asyncDeferred.cancel()//asyncDeferred协程取消

    })
/**
      如果在deferred.await()之后调用deferred.cancel()不会有任何情况发生,因为协程已经处理结束。
      asyncTest
      子协程async的执行返回结果值result=kotlin.Unit
      */

3.在已取消的deferred上调用await()会抛出JobCancellationException异常。因为await()是负责在协程处理结果出来之前一直将协程挂起,如果协程被取消了那么协程就不会继续进行计算也不会有结果产生。因此,在协程取消后调用await()会抛出JobCancellationException异常: 因为Job已经被取消。

 MainScope().launch(context = Dispatchers.Main,block={
            val asyncDeferred :Deferred = async(context = Dispatchers.IO, block = {
                delay(3000) // 在给定时间内延迟协程而不阻塞线程,并在指定时间后恢复协程。你可以认为它实际上就是触发了一个延时任务,告诉协程调度系统多久之后再来执行后面的代码。
                println("asyncTest")
                return@async
            })
             asyncDeferred.cancel()//asyncDeferred协程取消
            val result:Unit = asyncDeferred.await() //会抛出 JobCancellationException
            println(" 子协程async的执行返回结果值result=$result")


        })

协程作用域CoroutineScope

协程作用域(CoroutineScope)其实就是为协程定义的作用范围,
   为了确保所有的协程都会被追踪,Kotlin 不允许在没有使用CoroutineScope的情况下启动新的协程。
   它能启动新的协程,同时这个协程还具备上面所说的suspend和resume的优势。
   每个协程生成器launch、async等都是CoroutineScope的扩展,并继承了它的coroutineContext自动传播其所有元素和取消。

协程作用域本质是一个接口:

public interface CoroutineScope {
    //此域的上下文。Context被作用域封装,用于在作用域上扩展的协程构建器的实现。
    public val coroutineContext: CoroutineContext
}

 常用作用域CoroutineScope

runBlocking:  顶层函数,可启动协程。但是它会阻塞当前线程,主要用于测试。
 GlobalScope:  全局协程作用域,根协程, 通过GlobalScope创建的协程不会有父协程,
它启动的协程的生命周期只受整个应用程序的生命周期的限制,且不能取消,
 在运行时会消耗一些内存资源,这可能会导致内存泄露,所以仍不适用于业务开发。
coroutineScope    : 创建一个独立的协程作用域,直到所有启动的协程都完成后才结束自身。
  它是一个挂起函数,需要运行在协程内或挂起函数内。
  当这个作用域中的任何一个子协程失败时,这个作用域失败,所有其他的子程序都被取消。
supervisorScope: 该子协程的异常不会影响父协程,也不会影响其他子协程。
  作用域本身的失败(在block或取消中抛出异常)会导致作用域及其所有子协程失败,但不会取消父协程。
MainScope:  为UI组件创建主作用域。
   一个顶层函数,上下文是SupervisorJob() + Dispatchers.Main,说明它是一个在主线程执行的协程作用域,
 通过cancel对协程进行取消。推荐使用。
  MainScope作用域的好处就是方便地绑定到UI组件的声明周期上,在Activity销毁的时候mainScope.cancel()取消其作用域。

Lifecycle的协程支持

lifecycleScope

Lifecycle Ktx库提供的具有生命周期感知的协程作用域,与Lifecycle绑定生命周期,生命周期被销毁时,此作用域将被取消。

会与当前的UI组件绑定生命周期,界面销毁时该协程作用域将被取消,不会造成协程泄漏,推荐使用。

因为Activity 实现了LifecycleOwner这个接口,而lifecycleScope则正是它的拓展成员,可以在Activity中直接使用lifecycleScope协程实例:

viewModelScope ViewModel绑定生命周期,当ViewModel被清除时,这个作用域将被取消。推荐使用。

lifecycleScope  viewModelScope的依赖

// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
// 只有Lifecycles(没有 ViewModel 和 LiveData)
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"

协程的分类和行为规则

官方框架在实现复合协程的过程中也提供了作用域,主要用于明确父子关系,以及取消或者异常处理等方面的传播行为。该作用域分为以下三种:

顶级作用域 其就为最顶级,没有父协程 作用域
协同作用域

(父)协程中启动新的(子)协程, 子协程所在的作用域默认为协同作用域。

子协程抛出的未捕获异常,都将传递给父协程处理,父协程同时也会被取消。

1.父协程被取消,则所有子协程均被取消。

2.父协程需要等待子协程执行完毕之后才会最终进入完成状态,不管父协程自身的协程体是否已经执行完。

3.子协程会继承父协程的协程上下文中的元素,如果自身有相同key的成员,则覆盖对应的key,覆盖的效果仅限自身范围内有效。

主从作用域 (父)协程中启动新的(子)协程, (子)协程作用域出现的异常,不会将异常向上传递给父协程。

1.父协程被取消,则所有子协程均被取消。

2.父协程需要等待子协程执行完毕之后才会最终进入完成状态,不管父协程自身的协程体是否已经执行完

3.子协程会继承父协程的协程上下文中的元素,如果自身有相同key的成员,则覆盖对应的key,覆盖的效果仅限自身范围内有效。

调度器CoroutineDispatcher

 调度器也属于协程上下文 context: CoroutineContext

 协程的在哪挂起    什么时候恢复,为什么能切换线程,这因为调度器的作用

 协程只有在挂起点的位置才能进行调度

调度的本质就是解决挂起点恢复之后的协程逻辑在哪里运行的问题。

调度器模式 名称 说明 适用场景

context=

Dispatchers.Default

默认调度器
默认调度器,非主线程。CPU密集型任务调度器,适合处理后台计算。 通常处理一些单纯的计算任务,或者执行时间较短任务比如:Json的解析,数据计算等。

context=

Dispatchers.Main

主线程调度器

UI调度器, Andorid 上的主线程。

调度程序是单线程的,通常用于UI交互,刷新等。

context=

Dispatchers.Unconfined

任意调度器
一个不局限于任何特定线程的协程调度程序,即非受限调度器。 子协程切换线程代码会运行在原来的线程上,协程在相应的挂起函数使用的任何线程中继续。

context=

Dispatchers.IO

IO调度器
IO调度器,非主线程,执行的线程是IO线程。 适合执行IO相关操作,比如:网络处理,数据库操作,文件读写等。

                        

 //创建一个在主线程执行的协程作用域
          MainScope().launch(context = Dispatchers.Main, block = {
              println("MainScope Thread: ${Thread.currentThread().name}") //  MainScope Thread: main
              println(" MainScope当前协程的调用者对象this=$this") //  MainScope当前协程的调用者对象this=StandaloneCoroutine{Active}@3457c62

               launch(context = Dispatchers.Main, block = {
                   println("Dispatchers.Main  Thread: ${Thread.currentThread().name}") //Dispatchers.Main  Thread: main
                   println(" Dispatchers.Main  当前协程的调用者对象this=$this") //  Dispatchers.Main  当前协程的调用者对象this=StandaloneCoroutine{Active}@d02fcae
               })

              launch(context = Dispatchers.Default, block = {
                  println("Dispatchers.Default  Thread: ${Thread.currentThread().name}") //Dispatchers.Default  Thread: DefaultDispatcher-worker-1
                  println(" Dispatchers.Default  当前协程的调用者对象this=$this") //  Dispatchers.Default  当前协程的调用者对象this=StandaloneCoroutine{Active}@64801f3
              })

              launch(context = Dispatchers.Unconfined, block = {
                  println("Dispatchers.Unconfined  Thread: ${Thread.currentThread().name}") // Dispatchers.Unconfined  Thread: main
                  println(" Dispatchers.Unconfined  当前协程的调用者对象this=$this") //  Dispatchers.Unconfined  当前协程的调用者对象this=StandaloneCoroutine{Active}@2330eb0
              })

              launch(context = Dispatchers.IO, block = {
                  println("Dispatchers.IO  Thread: ${Thread.currentThread().name}") //  Dispatchers.IO  Thread: DefaultDispatcher-worker-1
                  println(" Dispatchers.IO  当前协程的调用者对象this=$this") //  Dispatchers.IO  当前协程的调用者对象this=StandaloneCoroutine{Active}@a571129
              })


          })

withContext

在 Andorid 开发中,我们常常在子线程中请求网络获取数据,然后切换到主线程更新UI。官方为我们提供了一个withContext顶级函数,在获取数据函数内,调用withContext(Dispatchers.IO)来创建一个在IO线程池中运行的块。您放在该块内的任何代码都始终通过IO调度器执行。由于withContext本身就是一个suspend函数,它会使用协程来保证主线程安全。

由于withContext可让在不引入回调的情况下控制任何代码行的线程池,因此可以将其应用于非常小的函数,如从数据库中读取数据或执行网络请求。一种不错的做法是使用withContext来确保每个函数都是主线程安全的,那么可以从主线程调用每个函数。调用方也就无需再考虑应该使用哪个线程来执行函数了。您可以使用外部 withContext来让 Kotlin 只切换一次线程,这样可以在多次调用的情况下,以尽可能避免了线程切换所带来的性能损失。

 在Activity界面的主线程操作使用 withcontext

 MainScope().launch(context=Dispatchers.Main,block={ //todo 开始协程:主线程
         val  loginRegisterResponseBean : BaseResponseBean
                = withContext(context = Dispatchers.IO, block = {  // todo 切换到IO 线程:网络请求
                 APIClient.instance.initRetrofit(WanAndroidAPI::class.java).loginActionCoroutine("Derry-vip", "123456")
             })

         //todo 恢复到主线程 :更新 UI
         mBinding?.textView?.text=loginRegisterResponseBean.toString()
         mBinding?.textView?.setTextColor(Color.MAGENTA)
     })

  

 MainScope().launch(context=Dispatchers.IO,block={ //todo 开始协程:搞到子协程里
                delay(4000)
            val  loginRegisterResponseBean : BaseResponseBean
                    = withContext(context = Dispatchers.IO, block = {  // todo 切换到IO 线程:网络请求
                APIClient.instance.initRetrofit(WanAndroidAPI::class.java).loginActionCoroutine("Derry-vip", "123456")
            })

            //todo 恢复到主线程 :更新 UI
             withContext(context=Dispatchers.Main, block = {
                 mBinding?.textView?.text=loginRegisterResponseBean.toString()
                 mBinding?.textView?.setTextColor(Color.CYAN)
             })
            })
GlobalScope.launch(Dispatchers.Main) {//开始协程:主线程
    val result: User = withContext(Dispatchers.IO) {//网络请求(IO 线程)
        userApi.getUserSuspend("FollowExcellence")
    }
    tv_title.text = result.name //更新 UI(主线程)
}

协程上下文context: CoroutineContext(就是协程对象本身)

协程上下文主要承载着资源获取,配置管理等工作,是执行环境的通用数据资源的统一管理者。它有很多作用,包括携带参数,拦截协程执行等等。

协程上下文CoroutineContext的子类 作用
context=Job 协程的句柄,对协程的控制和管理生命周期。
context=CoroutineName 协程的名称, 用于方便调试和定位问题:
context=CoroutineDispatcher 调度器,确定协程在指定的线程来执行。
context=CoroutineExceptionHandler 协程异常处理器,处理未捕获的异常。

  //TODO  context: CoroutineContext =CoroutineName( val name: String) 协程上下文context: 指定协程上下文名称
        GlobalScope.launch(context = CoroutineName("GlobalScope1号"),block={
            println("获取当前协程对象this=$this, 当前协程名称coroutineName =$coroutineContext[CoroutineName]")

            launch(context = CoroutineName("launch1号"),block={
                println("获取当前协程对象this=$this, 当前协程名称coroutineName =$coroutineContext[CoroutineName]")
              })
        })

  

 //TODO  context: CoroutineContext = Dispatchers.IO   //  IO: CoroutineDispatcher   协程上下文context: 指定IO线程
         GlobalScope.launch(context = Dispatchers.IO,block={
             //TODO  context: CoroutineContext = Dispatchers.Main   //  Main: CoroutineDispatcher   协程上下文context: 指定Main线程
             launch(context=Dispatchers.Main,block={})
         })

多个上下文组合合并

协程上下文context  要传递多个参数,

使用 “+”对传递的多个上下文参数进行合并组合

      context : CoroutineContext= CoroutineName("GlobalScope2号")+Dispatchers.Main
      context : CoroutineContext= CoroutineName("launch2号")+Dispatchers.IO
  GlobalScope.launch(context = CoroutineName("GlobalScope2号")+Dispatchers.Main,block={
              println("获取当前协程对象this=$this,输出当前协程组合上下文context=$coroutineContext") 
               // 获取当前协程对象this=StandaloneCoroutine{Active}@2330eb0,输出当前协程组合上下文context=[CoroutineName(GlobalScope2号), StandaloneCoroutine{Active}@2330eb0, Dispatchers.Main]
              
              launch(context = CoroutineName("launch2号")+Dispatchers.IO,block={
                  println("获取当前协程对象this=$this, 输出当前协程组合上下文context=$coroutineContext")
                  // 获取当前协程对象this=StandaloneCoroutine{Active}@a571129, 输出当前协程组合上下文context=[CoroutineName(launch2号), StandaloneCoroutine{Active}@a571129, Dispatchers.IO]
              })
          })

协程启动模式CoroutineStart

CoroutineStart是一个枚举类,为协程构建器定义启动选项。在协程构建的start参数中使用,

启动模式 含义 说明

start: CoroutineStart =

CoroutineStart.DEFAULT(常用)

默认启动模式,立即根据它的上下文调度协程的执行 是立即调度,不是立即执行,DEFAULT 是饿汉式启动,launch 调用后,会立即进入待调度状态,一旦调度器 OK 就可以开始执行。如果协程在执行前被取消,其将直接进入取消响应的状态。

start: CoroutineStart =

CoroutineStart.LAZY(常用)

懒启动模式,启动后并不会有任何调度行为,直到我们需要它执行的时候才会产生调度 包括主动调用该协程的startjoin或者await等函数时才会开始调度,如果调度前就被取消,协程将直接进入异常结束状态。

 start: CoroutineStart =

CoroutineStart.ATOMIC

类似[DEFAULT],以一种不可取消的方式调度协程的执行 虽然是立即调度,但其将调度和执行两个步骤合二为一了,就像它的名字一样,其保证调度和执行是原子操作,因此协程也一定会执行

 start: CoroutineStart =

CoroutineStart.UNDISPATCHED

类似[ATOMIC],立即执行协程,直到它在当前线程中的第一个挂起点。 是立即执行,因此协程一定会执行。即使协程已经被取消,它也会开始执行,但不同之处在于它在同一个线程中开始执行。

协程取消

 协程几种取消方式:

1.调用 cancel 方法取消

  ⑴协程通过抛出一个特殊的异常CancellationException来处理取消操作。在调用job.cancel时你可以传入一个 CancellationException实例来提供指定错误信息。子协程会通过抛出异常的方式将取消的情况通知到它的父协程。父协程通过传入的取消原因来决定是否来处理该异常。如果子协程因为CancellationException而被取消的,那么对于父协程来说不需要进行额外操作。

public fun cancel(cause: CancellationException? = null)

该参数是可空的,如果不传参数则会使用默认的defaultCancellationException()作为参数。

⑵取消 一个作用域 (scope) ,会取消所有其已创建的所有子协程。

  取消作用域会取消它的所有子协程。 已取消的作用域无法再创建协程。

  //创建作用域
 val scope = CoroutineScope(Dispatchers.Main)

 //启动一个协程
 val job = scope.launch {
     delay(4000)
     println("执行子协程job")
 }

 val job2 = scope.launch {
     delay(4000)
     println("执行子协程job2")
 }

 //作用域取消 子协程  job job2 不在执行
scope.cancel()

⑶如果仅仅是因为要取消某个进行中的任务而取消其中某一个协程,

如果只取消某一个子协程,那么其他的兄弟子协程不受影响不会取消

  //创建作用域
        val scope = CoroutineScope(Dispatchers.Main)

        //子协程job1将会被取消,而另一个子协程job2则不受任何影响
        val job1 = scope.launch {
            delay(4000)
            println("执行子协程job1")
        }
        val job2 = scope.launch {
            delay(4000)
            println("执行子协程job2")
        }

        //取消单个子协程job1
        job1.cancel()   // 输出日志: 执行子协程job2

  注意:

如果使用的是androidx KTX库的话,在大部分情况下都不需要创建自己的作用域,所以也就不需要负责取消它们。 viewModelScopelifecycleScope都是 CoroutineScope对象,它们都会在适当的时间点被取消。当ViewModel被清除时,在其作用域内启动的协程也会被一起取消。lifecycleScope会与当前的UI组件绑定生命周期,界面销毁时该协程作用域将被取消,不会造成协程泄漏。

2.JOB协程的状态检查

  Job的生命周期

 协程知识点_第3张图片

一个Job可以包含一系列状态: 新创建 (New)、活跃 (Active)、完成中 (Completing)、已完成 (Completed)、取消中 (Cancelling) 和已取消 (Cancelled)。虽然我们无法直接访问这些状态,但是我们可以访问Job的属性: isActive、isCancelled 和 isCompleted。

 ⑴如果协程处于活跃状态,协程运行出错或者调用job.cancel()都会将当前任务置为取消中 (Cancelling) 状态 (isActive = false, isCancelled = true)

⑵当所有的子协程都完成后,协程会进入已取消 (Cancelled) 状态,此时 isCompleted = true。

 ⑶ 协程所处理的任务不会仅仅在调用cancel方法时就立马停止,相反,我们需要修改代码来定期检查协程是否处于活跃状态。在处理任务之前添加对协程状态的检查:

 while (i < 5 && isActive) //当job是活跃状态继续执行

MainScope().launch(context = Dispatchers.Main,block={
            val startTime = System.currentTimeMillis()
            val job = launch(context = Dispatchers.Default, block = {
                     var nextPrintTime = startTime
                     var i = 0
                    // while (i < 5) { //打印前五条消息 有个bug 就是协程被取消了 但是第3 4 条依然打印了出来
                while (i < 5 && isActive) { //当子协程job是活跃状态下(isActive = true)才会打印输出,  在job子协程取消后, 非活跃状态(isActive = false) 已取消,第3和第4条数据不会被打印出来。
                         if (System.currentTimeMillis() >= nextPrintTime) {//每秒钟打印两次消息
                             println("job:执行打印  ${i++} ...")  //  job:执行打印  1 ...   job:执行打印  2 ...
                             nextPrintTime += 500
                         }
                     }
             })

            delay(1200)//延迟1.2s
            println("等待1.2秒后")
            job.cancel()
            println("协程被取消")

        })
输出打印:
job:执行打印  1 ...
job:执行打印  2 ...
等待1.2秒后
协程被取消

3.join() & await() 的取消

   见上面 join( )与await()的区别

4.finally释放资源

当协程被取消后会抛出CancellationException异常,我们可以将挂起的任务放置于try…catch…finally代码块中,catch中捕获取消后抛出的异常,在finally代码块中执行需要做的清理任务。

val job = GlobalScope.launch {
    try {
        //TODO
        delay(500L)
    } catch (e: CancellationException) {
        print("协程取消抛出异常:$e")
    } finally {
        print("协程清理工作")
    }
}

job.cancel()//取消协程

[DefaultDispatcher-worker-1] 协程取消抛出异常:kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@bb81f53
[DefaultDispatcher-worker-1] 协程清理工作

5.NonCancellable

  如果协程被取消后需要调用挂起函数进行清理任务,可使用NonCancellable单例对象用于withContext函数创建一个无法被取消的协程作用域中执行。这样会挂起运行中的代码,并保持协程的取消中状态直到任务处理完成。

  但是这个方法需要慎用,这样做风险很高,因为可能会无法控制协程的执行。

MainScope().launch(context = Dispatchers.Main,block={
                  val job:Job = launch(context = Dispatchers.IO, block = {
                         try {
                              delay(500L)
                              println("协程被取消了")
                          } catch (e: CancellationException) {
                              println("协程取消抛出异常:$e")
                          }finally {
                             withContext(NonCancellable) {
                                 delay(100)//或者其他挂起函数
                                 println("协程清理工作")
                             }
                          }


                  })

               job.cancel()//取消协程
        })

  

/**
 *  协程取消抛出异常:kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@93cd052
 *  协程清理工作
 * */

 6.withTimeout

withTimeout 函数用于指定协程的运行超时时间,如果超时则会抛出TimeoutCancellationException,从而令协程结束运行。

withTimeout(1300) {//1300毫秒后超时抛出TimeoutCancellationException异常
    repeat(1000) { i ->
        println("I'm sleeping $i ...")
        delay(500)
    }
}

I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
Exception in thread "main" kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1300 ms

四、协程异常处理

1.try…catch

 ❶ 使用launch时,会直接抛出异常 崩溃 所以要将异常代码进行try..catch 就不会崩溃了

   

val coroutineScope:CoroutineScope = CoroutineScope(context = Dispatchers.Main)
    // todo launch()  有异常会崩溃,进行try catch()
    coroutineScope.launch {
             try {
                 println("模拟抛出一个数组越界异常")
                 throw IndexOutOfBoundsException() //launch 抛出异常
             } catch (e: Exception) {
                 //处理异常
                 println("执行catch捕捉住这个异常,不崩溃")
             }
     }

/**
 *  模拟抛出一个数组越界异常
 *  执行catch捕捉住这个异常,不崩溃
 * */

❷async被用作根协程 (CoroutineScope实例或supervisorScope的直接子协程) 时不会自动抛出异常,而是在调用await()时才会抛出异常。为了捕获其中抛出的异常,可以用try…catch包裹调用await()的代码:

  

 val coroutineScope2:CoroutineScope =  CoroutineScope(context = Dispatchers.Main)
              coroutineScope2.launch {
                  //todo 注意:这里很重要,这里async()和launch()协程是同级的,使用的都是根协程 CoroutineScope
                  // 协程语法规定:async被用作根协程 (CoroutineScope实例或supervisorScope的直接子协程) 时不会自动抛出异常,而是在调用await()时才会抛出异常。为了捕获其中抛出的异常,可以用try…catch包裹调用await()的代码:
                    try {
                          val deferred: Deferred = coroutineScope2.async{
                              println("模拟抛出一个算术运算异常")
                              throw ArithmeticException() //这里虽然有异常 但是不会抛出 所以程序不会崩溃
                              return@async
                          }
                        val result:Unit = deferred.await() // await() try catch了 程序就不会崩溃了
                  } catch (e: Exception) {
                      //处理异常
                      println("执行catch捕捉住这个异常,不崩溃")
                  }
              }

/**
     *     模拟抛出一个算术运算异常
          执行catch捕捉住这个异常,不崩溃

     * */

 ❸      根协程CoroutineScope的直接子协程是launch()【不是async()】, launch()的子协程是async(),
              协程语法规定: 当async被用作根协程时不会自动抛出异常外,但是如果async是其他协程的子协程,async中产生了一个异常,这个异常将就会被立即抛出。
              这个异常即使try catch了也捕捉不到,原因是async (包含一个Job在它的CoroutineContext中) 会自动传播异常到它的父级 (launch),这会让异常被立即抛出,最终崩溃。

 val coroutineScope2:CoroutineScope =  CoroutineScope(context = Dispatchers.Main)
        coroutineScope2.launch {
            //todo 注意:这里很重要, 根协程CoroutineScope的直接子协程是launch()【不是async()】, launch()的子协程是async(),
            // 协程语法规定: 当async被用作根协程时不会自动抛出异常外,但是如果async是其他协程的子协程,async中产生了一个异常,这个异常将就会被立即抛出。
            // 这个异常即使try catch了也捕捉不到,原因是async (包含一个Job在它的CoroutineContext中) 会自动传播异常到它的父级 (launch),这会让异常被立即抛出,最终崩溃。

            try {
                val deferred: Deferred =  [email protected]{
                    println("模拟抛出一个空指针异常")
                    throw NullPointerException()
                    return@async
                }

                val result:Unit = deferred.await()
            } catch (e: Exception) {
                 // async 中抛出的异常将不会在这里被捕获
                 // 但是异常会被传播和传递到 scope
                println("这里catch不会捕捉异常,异常向上传播,最终崩溃")
            }
        }

/**
     *  程序直接崩溃 报异常
     * */

 2.异常在作用域内的传播

协程的异常是会分发传播的,牵连到其他兄弟协程以及父协程。

当协程因出现异常失败时,它会将异常传播到它的父级,父级会取消其余的子协程,同时取消自身的执行。最后将异常再传播给它的父级。当异常到达当前层次结构的根,在当前协程作用域启动的所有协程都将被取消。

一般情况下这样的异常传播是合理的,但是在应用中处理与用户的交互,当其中一个子协程出现异常,那就可能导致所在作用域被取消,也就无法开启新的协程,最后整个UI组件都无法响应。

  如果一个子协程发生了异常,那么其他的兄弟子协程就会被取消,父协程也会被取消 为了解决这个问题,官方给出的解决方案就是使用SupervisorJob

coroutineScope 

 如果取消父协程或者父协程抛出异常,那么所有子协程都会被取消。

如果子协程被取消,则不会影响同级其他子协程和父协程。

如果子协程抛出异常,则会导致同级其他子协程被取消,将异常传递给父协程,父协程被取消,进而导致整个协程作用域失败。

比如一个网络请求失败了,所有其他的请求都将被立即取消,这种需求选择coroutineScope

supervisorScope /

SupervisorJob

❶ 它的取消操作只会向下传播,

一个子协程的运行异常失败,不会影响到其他子协程取消,内部的异常不会向上传播,不会影响父协程取消和兄弟协程的运行。

如果即使一个请求失败了其他的请求也要继续,则可以使用 supervisorScope,当一个协程失败了,supervisorScope是不会取消剩余子协程的。

 
  

SupervisorJob 独立户

SupervisorJob不会取消它自己和它的子协程

SupervisorJob不会传播异常并传递给它的父级,它会让子协程自己处理异常。

❸ SupervisorJob的一个子协程的运行失败或取消不会导致自己失败,也不会影响到其他子协程。

supervisorScope 作用域 子协程抛出的异常并没有影响父级作用域以及作用域内的其他子协程的执行

 coroutineScope的作用域使用案例

 try {
      coroutineScope {
          println("1")
          //第一个子协程job1   抛出异常NullPointerException    导致其他的子协程job2也会抛出取消异常CancellationException,而取消不会运行
          // 同时异常还会传递给父协程  导致父协程发生异常 而取消不会运行
           val job1:Job = launch {
               println("2")
               throw  NullPointerException()//抛出空指针异常
           }

          //第二个子协程   由于第一个子协程异常 导致第二个子协程 取消
          val job2 = launch {
              delay(1000)
              println("3")
          }

          // 使用 try…catch捕获  第二个子协程job2  抛出的取消异常CancellationException
          try {
              job2.join()
              println("4")//等待第二个子协程完成:
          } catch (e: Exception) {
              println("5.输出捕获第二个协程job2 的取消异常: ${e}")//捕获第二个协程job2 的取消异常 :kotlinx.coroutines.JobCancellationException:
          }

      }

    } catch (e: Exception) {//异常传递给父协程,父协程捕获异常
        println("6.异常传递给父协程,父协程捕获异常: $e") //异常传递给父协程,父协程捕获异常: java.lang.NullPointerException
    }
    Thread.sleep(3000)//阻塞主线程3秒,以保持JVM存活,等待上面执行完成

/**
  1
  2
  5.输出捕获第二个协程job2 的取消异常: kotlinx.coroutines.JobCancellationException: ScopeCoroutine is cancelling; job=ScopeCoroutine{Cancelling}@1ad282e0
  6.异常传递给父协程,父协程捕获异常: java.lang.NullPointerException
 *
 *
 * */
supervisorScope的作用域使用案例

try {
        // job1抛出的异常并没有影响父级作用域以及作用域内的其他子协程job2的执行
        supervisorScope {
            println("1")
             val job1:Job = launch {
                println("2")
                throw  NullPointerException()// 子协程job1抛出空指针异常
            }

             // 子协程job2不受影响 继续执行
            val job2 = launch {
                delay(1000)
                println("3")
            }


            try {
                job2.join()
                println("4")//等待第二个子协程完成:
            } catch (e: Exception) {
                println("5.输出捕获第二个协程job2 的取消异常: ${e}")
            }

        }

    } catch (e: Exception) {
        println("6.异常传递给父协程,父协程捕获异常: $e")
    }
    Thread.sleep(3000)//阻塞主线程3秒,以保持JVM存活,等待上面执行完成

/**
 * job1抛出的异常并没有影响父级作用域以及作用域内的其他子协程job2的执行
 *  1
    2
 *  3
    4
 *
 * */

3.异常处理器CoroutineExceptionHandler

CoroutineExceptionHandler异常处理器属于协程上下文的一种,需要将其添加到协程上下文中。 可以处理未捕获的异常。

异常如果需要被捕获,则需要满足下面两个条件:

  • 这个异常是被自动抛出异常的协程所抛出的 (是launch,而不是async);
  • CoroutineExceptionHandler设置在CoroutineScope的上下文中或者在一个根协程 (CoroutineScope或者supervisorScope的直接子协程) 中。

      

CoroutineScope的子协程上下文context设置异常处理器无效无意义
  CoroutineScope的子协程上下文context设置异常处理器coroutineExceptionHandler没有意义无效,不会打印数据,因为异常向上传递给父协程,传递给父协程的异常处理器coroutineExceptionHandler去处理异常
 所以父协程的上下文必须设置 异常处理器coroutineExceptionHandler 处理来自子协程的异常信息  否则子协程的传递来的异常无法捕获处理
SupervisorJob直接子协程的上下文context可以设置异常处理器
SupervisorJob直接子协程的上下文context可以设置异常处理器coroutineExceptionHandler 处理子协程自己内部的异常,因为SupervisorJob不会让异常向上传递

val coroutineExceptionHandler = CoroutineExceptionHandler { context, exception ->
            println("捕获到的异常: $exception") // 捕获到的异常: java.lang.NullPointerException
        }

     runBlocking {
                  val coroutineScope = CoroutineScope(context=Job())
                         //上下文组合 context = Dispatchers.IO+coroutineExceptionHandler
                         //父协程中设置异常处理器coroutineExceptionHandler  是有效的 处理来自子协程的异常信息
               val job = coroutineScope.launch(context = Dispatchers.IO + coroutineExceptionHandler, block = {
                                  //子协程launchJob抛出空指针异常
                                   //CoroutineScope的子协程上下文context设置异常处理器coroutineExceptionHandler没有意义无效,不会打印数据,因为异常向上传递给父协程,传递给父协程的异常处理器coroutineExceptionHandler去处理异常
                                  //所以父协程的上下文必须设置 异常处理器coroutineExceptionHandler 处理来自子协程的异常信息  否则子协程的传递来的异常无法捕获处理
                               // val launchJob:Job = launch(context=Dispatchers.IO + coroutineExceptionHandler, block = {
                                 val launchJob:Job = launch(context=Dispatchers.IO , block = {
                                    throw NullPointerException()
                                })
                               //没有任何效果,执行await()会异常崩溃
                              val asyncDeferred:Deferred = async {
                                throw IllegalArgumentException()
                                return@async
                             }
                              val result:Unit= asyncDeferred.await() //这里会崩溃

            })
                job.join() //暂停协程,直到任务完成

            println("============================================")
            supervisorScope {
               //todo  SupervisorJob直接子协程的上下文context可以设置异常处理器coroutineExceptionHandler 处理子协程自己内部的异常,因为SupervisorJob不会让异常向上传递
              val launchJob:Job = launch(context=Dispatchers.IO + coroutineExceptionHandler, block = {
                        throw ArithmeticException()//抛出算术运算异常
                })

            }

        }
/**
     * 捕获到的异常: java.lang.NullPointerException
     * 捕获到的异常: java.lang.ArithmeticException
     * */

注意:

1.协程内部使用CancellationException来进行取消,这个异常会被所有的处理者忽略,所以那些可以被catch代码块捕获的异常仅仅应该被用来作为额外调试使用的资源。

2.当协程的多个子协程因异常失败时,一般规则是"取第一个异常",因此将处理第一个异常。在第一个异常之后发生的所有其他异常都作为被抑制的异常绑定至第一个异常。

 五、Channel热数据流

 Channel是非阻塞的通信基础设施,它实际上就是一个队列,而且是并发安全的,可以用来连接协程,实现不同协程的通信

可以在两个或多个协程之间完成消息传递,多个作用域可以通过一个Channel对象来进行数据的发送和接收。类似于BlockingQueue+挂起函数,称为热数据流

Channel 的可以说为协程注入了灵魂。每一个独立的协程不再是孤独的个体, Channel 可以让他们更加方便的协作起来。

1.创建 Channel

  创建Channel的方式有两种:

  • 直接使用Channel对象创建,如上
  • 拓展函数produce:启动一个生产者协程,返回一个ReceiveChannel。它启动的协程在结束后会自动关闭对应的Channel

2.发送数据

  • channel.send():发送数据。
  • channel.close():关闭Channel,数据发送完毕。

3.接收数据

channel.receive():接收数据。

 MainScope().launch {
                // 1. 创建 Channel
                val channel = Channel()
                // 2. Channel 发送数据
                  launch(context=Dispatchers.IO,block={
                     for (i in 1..3) {
                          delay(100)
                          channel.send(i)//Channel 发送数据
                      }
                      channel.close()//关闭Channel,发送结束
                  })

                // 3. channel.receive():接收数据。
                // 一般调用 Channel#receive() 获取数据,但是这个方法只能获取一次传递的数据,如果我们必须知道获取数据的次数:
                launch(context = Dispatchers.IO,block={
                    //重复3次接收数据
                    repeat(times = 3,action={
                            val receive = channel.receive()//接收数据
                            println("接收数据 $receive")
                        })
                })

            }



  /**
     *   接收数据 1
     *   接收数据 2
     *   接收数据 3
     *
     * */

  

 MainScope().launch {
             // 1. produce创建一个 Channel
             // 拓展函数produce:启动一个生产者协程,返回一个ReceiveChannel。它启动的协程在结束后会自动关闭对应的Channel。
             // 拓展函数produce直接将创建Channel和发送数据合为一步了。
             val channel : ReceiveChannel =  produce(context = Dispatchers.IO, block = {
                 for (i in 1..3) {
                     delay(100)
                     this.send(i)//发送数据
                 }
            })



             // 2.在另一个协程里接受数据, 接收数据
              launch(context = Dispatchers.IO,block={
                  for (value in channel) {//for 循环打印接收到的值(直到渠道关闭)
                      println("接收数据: $value")
                  }
              })


         }



/**
 *  接收数据: 1
 * 接收数据: 2
 *  接收数据: 3
 * */

六、Flow冷数据流

所谓冷数据流,就是只有消费时才会生产的数据流。

  Flow是一种异步数据流,它按顺序发出值并正常或异常完成。是 Kotlin 协程的响应式API,类似于 RxJava 的存在Flow不会阻塞主线程的运行

Flow是一种冷数据流,流生成器中的代码直到流被收集起来才会运行。一个Flow创建出来之后,不消费则不生产,多次消费则多次生产,生产和消费总是相对应的。

Channel 热数据流

无观察者时,也会生产数据。你不订阅,它也会发送数据。

比如某场影片在电影院播放,你要去电影院看才能看到,你不去这场电影也是会正常放的;

Flow冷数据流

无消费者时,则不会生产数据。你触发了,它才有数据发送过来。

比如这场电影在网络上公开了,你不去播放他就不会播放,你主动播放了他才会播放。RxJava相对应的是协程的冷数据流Flow

1.基础 

   ⑴ 创建 Flow 对象的几种方法

Flow:  创建Flow的普通方法,从给定的一个挂起函数创建一个冷数据流。线程不安全
channelFlow 支持缓冲通道,线程安全,允许不同的CorotineContext发送事件。
T.asFlow()

将其他数据转换成普通的flow,一般是集合向Flow的转换。

(1..3).asFlow()创建Flow对象

flowof(vararg elements: T)

使用可变数组快速创建flow,类似于listOf()

flowof(1..3)创建Flow对象

   

   

⑵生产数据消费数据流程

数据源创建Flow)

Flow

 
flow(block: suspend FlowCollector.() -> Unit): Flow
collect订阅接收消费数据

collect()

接收收集器emit()发射的数据。

 
public interface Flow {
suspend fun collect(collector: FlowCollector)

}

emit发射

发送 数据

emit(value)

收集上游的数据并发出。不是线程安全,不应该并发调用。线程安全请使用channelFlow而不是flow

 
public fun interface FlowCollector { public suspend fun emit(value: T)
}

 lifecycleScope.launch(context = Dispatchers.IO,block={
          //1.创建一个Flow
          val  flow: Flow = flow {
                       for (i in 1..3) {
                           delay(200)
                           //2.从冷流中发出数据
                           this.emit(i)
                       }
                   }

          //3.从flow冷流中获取数据
          flow.collect(collector={
              println("从flow冷流中获取数据:$it")
          })

      })


/**
 *
 * 从flow冷流中获取数据:1
 * 从flow冷流中获取数据:2
 * 从flow冷流中获取数据:3
 *
 * */

 2.线程切换

改变发送数据emit的线程

Flow.flowOn(context: CoroutineContext): Flow

改变消费数据collect的线程 它自动切回所在协程的调度器

注意:不允许在内部使用withContext()来切换flow的线程。因为flow不是线程安全的,如果一定要这么做,请使用channelFlow

3.异常处理

Flow的异常处理也比较直接,直接调用 catch 函数捕获异常即可。

Flow的参数中抛了一个空指针异常,在catch函数中就可以直接捕获到这个异常。如果没有调用catch函数,未捕获异常会在消费时抛出。catch 函数只能捕获它的上游的异常。

注意:流收集还可以使用try{}catch{}块来捕获异常。

  Flow.catch(action: suspend FlowCollector.(Throwable) -> Unit): Flow

  lifecycleScope.launch {
         flow {
             this.emit(10) //从流中发出值
             throw NullPointerException()//抛出空指针异常
         }.catch(action={ println("flow捕获上游的异常 caught error: $it") })
           .collect{ println("收集获取数据:$it") }
     }

    /**
     *   收集获取数据:10
     *   flow捕获上游的异常 caught error: java.lang.NullPointerException
     * */

4.完成

如果我们想要在流完成时执行逻辑,可以使用 onCompletion函数:

Flow.onCompletion(action: suspend FlowCollector.(cause: Throwable?) -> Unit): Flow

注意:流还可以使用try{}finally{}块在收集完成时执行一个动作。

  

lifecycleScope.launch {
        flow{
            this.emit(10)
        }.onCompletion(action={ println("执行Flow 操作完成回调") }) //流操作完成回调
         .collect{ println("收集获取数据:$it") }
    }

     /**
      * 收集获取数据:10
      *  执行Flow 操作完成回调
      * */

  

5.取消

  Flow没有提供取消操作,Flow的取消依赖于collect末端操作符,而它们又必须在协程当中调用,因此Flow的取消主要依赖于末端操作符所在的协程的状态。

 想要取消Flow只需要取消它所在的协程。

  

 //创建一个作用域
        lifecycleScope.launch {
            //1.创建一个子协程
            val job  = launch {
                        //2.创建flow
                     val intFlow = flow {
                                     (1..6).forEach {
                                         delay(1000)
                                         //3.发送数据
                                         this.emit(it)
                                     }
                             }

                     //4.收集数据
                intFlow.collect{
                    println("输出收集获取数据:$it")
                }


             }

            //5.在3.5秒后取消协程job
             delay(3500)
            job.cancelAndJoin()
         }



/**
 *  输出收集获取数据:1
 *  输出收集获取数据:2
 *  输出收集获取数据:3
 *  //输出收集获取数据:4  //取消了flow所在的协程 不在输出
 *  //输出收集获取数据:5  //取消了flow所在的协程 不在输出
 *  //输出收集获取数据:6  //取消了flow所在的协程 不在输出
 *
 * */

  6.背压

  什么是背压?就是在生产者的生产速率高于消费者的处理速率的情况下出现,发射的数据量大于消费的数据量,造成了阻塞,就相当于压力往回走,这就是背压。只要是响应式编程,就一定会有背压问题。

  处理背压问题有以下三种方式:

处理背压问题的方式 说明

buffer 添加缓冲 

指定固定容量的缓存;

buffer指定一个容量。不需要等待收集执行就立即执行发射数据,只是数据暂时被缓存而已,提高性能,如果我们只是单纯地添加缓存,而不是从根本上解决问题就始终会造成数据积压。

conflate 保留最新值

collectLatest 

新值发送时,取消之前的

7.Flow操作符

  7.1 基本操作符

  

Flow 基本操作符 作用
map 转换操作符,将值转换为另一种形式输出
take 接收指定个数发出的值
filter 过滤操作符,返回只包含与给定规则匹配的原始值的流。

 7.2 flow末端操作符

flow末端流操作符 作用
collect 最基础的收集数据,触发flow的运行
toCollection 将结果添加到集合
launchIn 在指定作用域直接触发流的执行
toList 给定的流收集到 List 集合
toSet 给定的流收集到 Set 集合
reduce 规约,从第一个元素开始累加值,并将参数应用到当前累加器的值和每个元素。
fold 规约,从[初始]值开始累加值,并应用[操作]当前累加器值和每个元素

7.3功能性操作符

功能性操作符 作用
retry

重试机制 ,当流发生异常时可以重新执行

网络请求使用retry重试

cancellable 接收数据的时候判断 协程是否被取消 ,如果已取消,则抛出异常
debounce 防抖节流 ,指定时间内的值只接收最新的一个,其他的过滤掉。

7.4 回调操作符

回调流操作符 作用
onStart 在上游流开始之前被调用。 可以发出额外元素,也可以处理其他事情,比如发埋点
onEach 在上游向下游发出元素之前调用
onEmpty 当流完成却没有发出任何元素时回调,可以用来兜底。

7.5 组合操作符

组合流操作符 作用
zip 组合两个流,分别从二者取值,一旦一个流结束了,那整个过程就结束了
combine

组合两个流,在经过第一次发射以后,任意方有新数据来的时候就可以发射,另一方有可能是已经发射过的数据

数据合并使用combine

7.6展平流操作符

展平流有点类似于 RxJava 中的 flatmap,将你发射出去的数据源转变为另一种数据源。

展平流操作符 作用
flatMapConcat 串行处理数据,展开合并成一个流
flatMapMerge 并发地收集所有流,并将它们的值合并到单个流中,以便尽快发出值
flatMapLatest 一旦发出新流,就取消前一个流的集合

你可能感兴趣的:(协程,kotlin)