• Flow--冷流


    @file:Suppress("OPT_IN_IS_NOT_ENABLED")
    
    package com.wn.jetpack.coroutine
    
    import android.text.Editable
    import android.text.TextWatcher
    import android.widget.TextView
    import kotlinx.coroutines.*
    import kotlinx.coroutines.channels.awaitClose
    import kotlinx.coroutines.flow.*
    import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed
    
    /**
     * Created By wn at 2022/1/7 16:22
     * Flow   流     Java  Stream
     */
    
    /**
     *  listOf("a","b","c")
     *  代码顺序执行 如果listOf 耗时很久 就需要等待很久才可以继续执行 相当于阻塞
     *  a  b  c  都准备好才会 一并返回 list  给调用端
     *
     *  Sequence  序列
     *  获取每一个元素时都要执行一定的计算,这种计算是阻塞行为,将计算后的多个结果依次返回给 调用端
     *  1  不是一并返回  而是 生成一个 就返回一个给 调用端
     *  2  计算过程使用 主线程  会阻塞 主线程
     *
     *  是否 阻塞 主线程    是否一次性返回结果
     *  既不阻塞主线程  结果又依次 返回 --> Flow
     */
    // 阻塞主线程
    // SequenceScope 的扩展函数  只能使用指定的挂起函数  比如 yield   yieldAll
    // flow  可以调用挂起函数
    private fun myMethod(): Sequence<Int> = sequence {
    //  每休眠一秒  返回 一个数据 调用端 使用一个
      for (i in 100..106) {
        Thread.sleep(1000)
        yield(i)
      }
    }
    
    // 协程不会阻塞 线程  但是  一并返回 list
    private suspend fun myMethod2(): List<Int> {
      delay(1000)
      return listOf(1, 2, 3)
    }
    
    /*
     既不阻塞主线程   又是 产生一个 返回一个
     myMethod3  无需 suspend 标识   flow{ }是挂起函数  flow  可以调用挂起函数
     emit 函数发射出来  才会有之后的 中间--终止 操作
     collect  来收集
     延迟执行  只有调用终止(collect) 操作才会执行
     中间操作  不会执行
     终止操作先执行  当执行到 it 时  触发 构建与中间操作 代码执行  拿到 it值 终止操作继续执行
     */
    private fun myMethod3(): Flow<Int> = flow {
    //  每休眠一秒  返回 一个数据 调用端 使用一个
      for (i in 100..106) {
        delay(1000)
        emit(i)
      }
    }
    
    fun main0() {
      //myMethod() 序列 阻塞主线程  产生一个 返回一个
      myMethod().forEach { println(it) }
      //myMethod2() 协程不会阻塞 线程  但是  一并返回 list
      GlobalScope.launch {
        myMethod2().forEach { println(it) }
      }
    //  既不阻塞主线程   又是 产生一个 返回一个
      GlobalScope.launch {
        myMethod3().collect { println(it) }
      }
    }
    
    /*
    * Flow builder  流构建器
    * 1 flow{ } 经常使用
    * 2 flowOf()  定义发射固定数量值的流
    * 3 各种集合与序列 都停供了 asFlow() 扩展方法来转为 Flow
    * */
    private fun main2() = runBlocking {
      flowOf(1, 2, 3).collect { println(it) }
      (1..10).asFlow().collect { println(it) }
    }
    
    /*
    * Flow的中间操作符   与 Stream 思想一致
    * Flow 与 Sequence 中间操作符的区别 Flow的中间操作符的代码块内可以 运行 挂起函数
    * map 映射值  做改变用
    * filter  筛选用 true
    * onEach 遍历用 类似 forEach 作用于每一个,当多个操作符时 可以看中间状态
    * debounce(300)  防抖处理(输入框)  确保flow的各项数据之间存在一定的时间间隔 时间点过于临近的数据只会保留最后一条整体
    * sample(300) flow的数据流当中按照一定的时间间隔来采样某一条数据 某些源数据量很大,但我们又只需展示少量数据的时候比较有用
    * transform  转换  连续多次emit 连续多次执行
    * take(N)  限定数量  前N个元素   异常的方式结束 无需管
    *
    * flatMapConcat
    * flatMapMerge
    * flatMapLatest
    *
    * zip
    * buffer
    * conflate
    * */
    private fun main3() = runBlocking {
      (1..10)
        .asFlow()
        .filter { it > 2 }
        .map { delay(100) }
        .collect { println(it) }
    //  transform
      (1..2)
        .asFlow()
        .transform {
          emit(it + 1)
          emit(it + 2)
          emit(it + 3)
        }
        .collect { println(it) }// 234   345  1个变3个
    //  take 前2个元素
      flow {
        emit(1)
        emit(1)
        println("2222222222")//只取前两个 不会打印
        emit(1)
      }
        .take(2)
        .collect { println(it) }
    }
    
    /*
    * Flow 打平 flatMapConcat --Flow> -> Flow
    * flatMapConcat 一个挂起执完-flow1 再去 请求另一个 挂起-flow2 比如: 发起一个请求去获取token信息,然后再去获取用户数据  顺序执行
    * flatMapConcat必须顺序执行  总时间为最大时间
    * flatMapMerge{ flow{ delay emit }}   flow1 flow2并发执行,  flow1独立顺序执行  遇到 emit 就 触发 flow2 谁快先输出 谁
    * flatMapLatest  flow1 flow2并发执行,  flow2 接收flow1-emit (flow1中再次emit flow2没有处理完就丢弃 接收 新数据 ,数据不丢失但是没处理完丢弃)
    * 1次 还没处理完 第二次又来了 舍弃1所以只拿第二次 第三次还没处理 第四次又来了  舍弃3只拿 4
    * collectLatest 接收处理最新的数据。如果有新数据到来了而前一个数据还没有处理完,则会将前一个数据剩余的 处理逻辑及后面逻辑全部取消  数据不丢失
    * collectLatest   每一个数据都接收  干到哪算哪   丢下目前手里活  接收下一个  但是每一个都会接  干不干完 看速度
    * */
    private fun myMethod7(i: Int): Flow<String> = flow {
      delay(i.toLong())
      emit("$i->First")
      emit("$i->Second")
    }
    
    @OptIn(FlowPreview::class)
    private fun main10() = runBlocking {
      (1..2)
        .asFlow()
        .onEach { delay(100) }
        .flatMapConcat { myMethod7(it) }// 链接模式 按顺序执行{ flow{1->First,1->Second}, flow{2->First,2->Second} } =>flow{1,1,2,2}
        // flatMapMerge  并发执行  中 myMethod7(1) myMethod7(2)  最后 打平的 的顺序  安时间排序
        // .flatMapMerge { myMethod7(it) } 合并模式 { flow{1->First,2->First}, flow{1->Second,2->Second} } =>flow{1,2,1,2}
        //  不要中间值  前面 0 100 50  毫秒发射一次   flatMapLatest{中 80 毫秒处理一次} 第一次 处理完  第二次没处理完 第三次就来了 舍弃第二次数据
        // .flatMapLatest{}
        .collect { println(it) }
    }
    
    /*
    * zip
    * 场景 两个接口之间并没有 先后 依赖关系,但是却需要两个接口同时返回数据
    * 俩flow 同步执行  同一个时间点 flow1 + flow2 数据结合,
    * 时间短的 flow 数据全部处理结束就会终止运行,时间长的 flow 未处理的数据将不会得到处理
    * 可以连续  zip
    * 同一时间 flow1:a ,flow2:1 --a+1  下一时间 flow1:b ,flow2:2 --b+2 ,flow2结束  函数结束
    * */
    private fun main9() = runBlocking {
                val time = measureTimeMillis {
                    val flow1 = flow {
                        emit("a")
                        emit("b")
                        emit("c")
                        emit("d")
                    }
                    val flow2 = flow {
                        emit("1")
                        emit("2")
                    }
                    flow1.zip(flow2) {
                            sex, subject->"$sex-->$subject"
                    }.collect {
                        println(it)
                    }
                }
                println("use time:$time")
            }
    }
    //打印结果
    a-->1
    b-->2
    use time:71
    
    /*
    * combine
    * 场景 两个接口之间并没有 先后 依赖关系,但是却需要两个接口同时返回数据
    * 与zip 的唯一区别: 短的 flow 等待长 flow 结束后才结束
    * 同一时间 flow1:a ,flow2:1 --a+1  下一时间 flow1:b ,flow2:2 --b+2 
    * flow2 结束 flow1 继续 emit 参与 flow2:2
    * */
    
             fun test016() {
            runBlocking {
                val time = measureTimeMillis {
                    val flow1 = flow {
                        emit("a")
                        emit("b")
                        emit("c")
                        emit("d")
                    }
                    val flow2 = flow {
                        emit("1")
                        emit("2")
                    }
                    flow1.combine(flow2) {
                            sex, subject->"$sex-->$subject"
                    }.collect {
                        println(it)
                    }
                }
                println("use time:$time")
            }
        }
    //打印结果
    a-->1
    b-->2
    c-->2
    d-->2
    use time:45
    
    /*
    * Buffer  缓冲
    * flow 与 collect  相当于运行在同一个协程当中,因此collect函数中的代码没有执行完,下一次flow函数中的代码也会被挂起等待
    * collect 一执行(终止操作符触发 flow 开始 --collect)  每一次都是 flow--中间操作符--collect
    * collect立刻触发  flow 执行  emit一执行  立刻触发 中间操作符  onEach  但是 位于同一协程 要 下一轮顺序执行需等待本次collect 完毕
    * 1 ready 立刻 1S 1handle  1S 2 ready  1S 2handle 1S 3 ready  1S 3handle
    *
    * buffer函数会让flow函数和collect函数运行在不同的协程当中
    * collect 一执行  同时触发 flow 与 collect 同步执行
    * */
    fun main() = runBlocking {
      flow {
        emit(1);
        delay(1000);
        emit(2);
        delay(1000);
        emit(3);
      }.onEach {
        println("$it is ready")
      }.collect {
        delay(1000)
        println("$it is handled")
      }
    }
    
    /**
     * Buffer  缓冲  相当于 并发执行   提高了生产速度
     * collect 一执行  同时触发 flow 与 collect 同步执行 
     * buffer 构造了新的协程执行flow闭包,上游数据会发送到 缓冲区里
     * 发送完成继续发送下一条
     * collect操作符监听缓冲区是否有数据,若有则收集成功
     * 
     * 1ready 立刻 1秒后 2ready 1handle 1s-3ready 2handle  1s-3handle
     */
    fun bufferTest() {
      runBlocking {
        flow {
          emit(1);
          delay(1000);
          emit(2);
          delay(1000);
          emit(3);
        }
          .onEach {
            println("$it is ready")
          }
          .buffer()
          .collect {
            delay(1000)
            println("$it is handled")
          }
      }
    }
    
    /*
    * 背压 与流的方向一致 加速流的流动
    * 流的产生 速度 快于  流的收集速度  就会产生背压-->降低生产速度  或者 提高收集速度
    * flowOn(Dispatchers.Default)  切换协程
    * 提高收集速度=========
    * conflate() collect 接收到的值 必须处理完毕  只接收最新的值  中间值  丢弃 -- {collect 的耗时 只接收 最新的 emit}
    * conflate 借助 buffer 实现  只是限定 每次读取 buffer时 只有一个最新数据 
    * collectLatest{ 收集端 忽略处理耗时操作以及其后面操作  处理不完放下 接收下一个  提高 收集速度 数据不缺失 }
    * */
    private fun main8() = runBlocking {
      flow {
        (1..3).forEach {
          emit(it)
          delay(1000)
        }
      }.conflate()
        .collect {
          delay(2000)
          println(it) // 1 3
        }
    }
    
    /*
    * 终止操作
    * 全部都是挂起函数  终止操作后 才会真正的开始执行流的收集
    * reduce{acc, value -> acc + value}  acc是累积值 0开始,value则是当前值 it
    * fold(initial) { acc, value -> acc + value }  acc = initial
    * collect
    * toList  toSet
    * 获取第一个元素 first()
    * */
    private fun main4() = runBlocking {
      (1..4)
        .asFlow().apply { }
        .map { it * it }
    //    .reduce { x, y -> x + y }// x=0 y=it x=return y = it
        .fold(1) { x, y -> x + y } // x=1 y=it x=return y = it
    }
    
    /*
    * 冷流--只有终止操作执行后  flow{ XXX }构建器 以及 中间操作 才会执行
    * */
    
    /*
    * Flow 中的 元素 是顺序执行  第一个--中间--中间(filter false结束)--终止--下一个
    * Flow的 collect 它运行在调用终止操作的协程上,默认不会开启新的协程
    * 每个emit 元素都会 依次 由所有中间操作处理最后进行终止操作 由 上游 进入到 下游
    * */
    private fun main5() = runBlocking {
      (1..10)
        .asFlow()
        .filter {
          println(it)
          it % 2 == 0
        }
        .map {
          println(it)
          it
        }
        .collect { println(it) }//1 2 2 2 3 4 4 4  中间去除 中间--中间--终止
    }
    
    /*
    * Flow Context  上下文
    * Flow的收集发生在 其调用的协程的上下文中  既collect执行所在的挂起函数或协程上下文 Context Preservation(上下文保留)
    * main6 阻塞主线程
    * 一般主线程收集 更新页面  IO线程一步获取数据  所以应该 不是同一个才对  flowOn
    * */
    private fun main6() = runBlocking {
      flow {
        println(Thread.currentThread().name)// runBlocking main
        emit(1)
      }.collect { println(Thread.currentThread().name) }// runBlocking main
    }
    
    /*
    * Flow 的收集和发射 必须位于同一个线程中  否则报错
    * 一般主线程收集 更新页面  IO线程异步获取数据  所以应该 不是同一个才对  flowOn
    * flowOn 改变 emit 的上下文,可以与 collect 不同,创建另外一个的协程
    * collect位于一个协程中  emit 位于另一个协程中
    * */
    private fun myMethod5() = flow {
      /* withContext(Dispatchers.Default) {
         emit(1)// Default
       }*/
      emit(1)
    }.flowOn(Dispatchers.Default)// Thread Default   coroutine2
    
    private fun main7() = runBlocking {
      myMethod5().collect { println(it) }// Thread main  coroutine1
    // launchIn 取代 collect   相当于直接构建了一个协程 不是runBlocking 的子协程
    // launchIn(this) runBlocking 子协程  无需join   cancel 可以取消
    // 执行 flow{ } 以及中间操作 onEach{ it }
      val job: Job = myMethod5().onEach { it + 1 }.launchIn(CoroutineScope(Dispatchers.IO))
      job.join()
      job.cancelAndJoin()
    }
    
    /*
    * Flow 的取消
    * Flow 的取消 与 协程的 取消 是一种协同关系,它自身没有引入任何新的取消点概念,它的取消是完全透明的
    * Flow的收集操作 collect 是可取消的,前提是Flow在一个可取消的挂起函数中被挂起了,除此之外无法取消
    * flow 是挂起代码块 collect 是挂起函数 运行在协程中  协程取消 挂起函数取消 同步取消
    * */
    private fun myMethod4(): Flow<Int> = flow {
    //  每休眠一秒  返回 一个数据 调用端 使用一个
      for (i in 100..106) {
        delay(1000)
        emit(i)
      }
    }
    
    private fun main1() = runBlocking {
      withTimeoutOrNull(2800) {
        myMethod4().collect { println(it) }
      }
    //  流 的取消 检测  每一次都会检测 所以 cancel() 后 就取消了    CoroutineScope.cancel
      myMethod4().collect { if (it == 3) cancel() }
    //  密集型的 不会每一次检测 无法取消  加上 cancellable 确保每一次都检测 可以取消 影响性能
      (1..5).asFlow().cancellable().collect { if (it == 3) this.cancel() }
    }
    
    /*
    * 异常处理
    * try catch  可以捕获 Flow 所有过程的 异常 -- 定义 中间 终止 -->调用 check
    * check false 抛出异常给  catch 块  并将输出结果作为 异常的一部分
    * try catch 包裹 终止操作,check 为假的地方都可以捕获
    * 声明式 catch 捕获上游的异常    命令式 try catch  捕获 下游异常
    * */
    private fun myMethod8(): Flow<String> = flow {
      emit(1)
      emit(2)
    }.map {
      check(it <= 1) { "$it 返回给异常" }
      "$it"
    }.catch {
      println(it)
      emit("catch 中 恢复 ")
    }
    
    private fun main11() = runBlocking {
      try {
        myMethod8().collect { println(it) }
      } catch (e: Throwable) {
        println(e)// 2 就会抛出异常 且携带 : 2 返回给异常
      }
    }
    
    /*
    * Flow 的完成
    * 1 命令式
    * 2 声明式
    * */
    // 1 命令式  只有这一种 finally  最终会执行  finally 代码块
    private fun myMethod9() = (1..10).asFlow()
    private fun main12() = runBlocking {
      try {
        myMethod9().collect {}
      } finally {
        println("完成")
      }
    }
    
    /*
    * 2 声明式
    * onCompletion 中间操作  但是 只有 Flow 完成收集操作才会调用
    * onCompletion 的默认参数 cause  不为空  就是有异常
    * onCompletion 即能看到Flow上游的异常信息  也能看到下游的异常信息
    * 只能看到异常  cause    不能捕获
    * 异常捕获  catch 只能捕获自己上游的异常  下游的 try catch
    * */
    private fun main00() = runBlocking {
      val flow = myMethod9().onCompletion { cause ->
        if (cause != null)
          println("只有终止操作后我才会调用 虽然我是中间操作 $cause")
      }
        /*   .map {
             check(it <= 1) { "check false cause is it" }
             it
           }*/
        .catch { cause -> println(cause) }
        .map {
          check(it <= 1) { "check false cause is it 2222222222222" }
          it
        }
      /*.collect { println(it) } // 1..10  只有终止操作后onCompletion 才会调用 虽然它是中间操作*/
      try {
        flow.collect {
    //      check(it <= 1) { "check false cause is it 111111111" }
          println(it) // 只输出1 collect 全部执行完毕 onCompletion 捕获不到异常  cause 有内容
        }
      } catch (e: Throwable) {
        println(e)
      }
    }
    
    /**
     * flow 的emit 与 collect 必须处于同一线程  flowOn
     * channelFlow -- send 可以处于不同线程
     * send 发送消息
     * withContext  切换线程
     */
    @ExperimentalCoroutinesApi
    fun myChannelFlow() = channelFlow {
      send(1)
      withContext(Dispatchers.IO) {
        send(2)
      }
    }/*.collect { println(it) }*/
    
    /**
     * callbackFlow
     * 将基于回调的 API 转换为数据流。callbackFlow 是冷流,没有接收者--collect,不会产生数据。
     * 重点是  基于 回调  所以可以用在回调函数中 转换数据流
     * 要在 callbackFlow block 体中,调用 awaitClose 方法  相比于channelFlow 多了 awaitClose 这步操作
     * 用来释放资源,比如取消网络请求、断开io流、移除监听器、释放内存 等等。
     * awaitClose 需要其flow所在作用域结束 即collect 所在作用域 才会 执行退出
     * send  : 必须在协程体内使用
     * offer : 可以在非协程体内使用
     */
    @ExperimentalCoroutinesApi
    //    获取关键字   边写边获取  editText 继承 TextView
    //    将 获取的关键字 包装成 flow  使用 callbackFlow
    private fun TextView.textWatcherFlow(): Flow<String> = callbackFlow {
      val textWatcher = object : TextWatcher {
        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
    
        // 将 元素添加到  flow  offer   回调函数中 转换数据流 冷流
        override fun afterTextChanged(s: Editable?) {
          offer(s.toString())
        }
      }
      addTextChangedListener(textWatcher)
    //    callbackFlow末尾使用awaitClose无 论携程被取消或者flow被关闭,都会执行这个代码块,
    //    可以在这个代码块里面进行一些资源释放的操作等等,防止内存泄漏
    //    flow 销毁时 移除监听
      awaitClose { removeTextChangedListener(textWatcher) }
    }
    /**
     *  微信收藏
     *  HOME 键 界面退居 后台     横竖屏 更改配置 是销毁重建
     *  UI 却并没有 挂起 且  viewModel--flow--emit 还在继续 发射
     *  这是非常危险的事情,因为在非前台的情况下更新UI,某些场景下是会导致程序崩溃
     *
     *  launchWhenStarted 替代它的话,情况会稍微好一些,因为它会在处于后台时将收集挂起--UI挂起。
     *  但这样 flow-emit 依旧活跃 持续发送数据 但是flow是冷流 没有 collect 就不应该发送数据
     *  这样发送的数据
     *  1 flow管道中保存过时 数据 -- 当 UI 活跃时会接着 pause时接收 旧数据
     *  2 从而将内存占满。尤其是 一个Flow可能又是由多个上游Flow合并而成--程序进入了后台,却仍有大量Flow依然处于活跃的状态,那么内存问题会变得更加严重
     *
     *  使用 repeatOnLifecycle 或 flowWithLifecycle
     *  UI 与 Flow 在 界面退居后台时  都会停止工作  回到前台后  Flow 从 0 开始工作 从头开始   flow是冷流 停止 就没数据
     *
     *  channelFlow flow -- 冷流         内存中没有数据  只有 collect 时 才会产生,存储在内存中,等收集完后就会自动回收
     *  StateFlow SharedFlow -- 热流   不收集的时候,也能产生数据,并把产生后就存储在内存中,等到收集完后,再把数据进行回收
     *  可以保持数据 设置超时时间   低于 超时时间  StateFlow 并不会重启
     *  repeatOnLifecycle 和 flowWithLifecycle 是 lifecycle-runtime-ktx 库在 2.4.0 稳定版
     */
    
    /**
     * StateFlow  热流   相似 LiveData   必须要有初始值
     * value 的改变 相当于 emit
     *
     * Home 键  是  Pause Resume
     * 当设备旋转或者接收到配置变更时,所有的 Activity 都可能会--重启 ,但 ViewModel生命周期贯穿 却能被保留,
     *
     * viewModel中-->repository-->retrofit--> flow { emit(data) }
     * 设备旋转之后会再次 onCreate 调用,冷流 重新 collect  从头开始工作
     *
     * 需要某种缓冲区机制来保障无论 -- 重新收集 --多少次都可以保持数据,并在多个收集器之间共享数据,
     * 而 StateFlow 正是为了此用途而设计的
     * 能够放心地将其与 Activity 或 Fragment 一起使用
     *
     * WhileSubscribed( 5000 )
     * 后台 或者 屏幕旋转  即 UI 暂停
     * FLow 暂停 不超过5秒 flow 不停止(重新开始 数据继续)  超过  停止  (重新开始 数据从头开始)
     */
    suspend fun myStateFlow() {
      val number = MutableStateFlow(1)
      number.value++
      number.value--
      number.map { "$it" }
        .collect { println(it) }
    }
    
    class StateFlowTest(viewModelScope: CoroutineScope) {
      private lateinit var _myUiState: MutableStateFlow<Int>
      val myUiState: StateFlow<Int> = _myUiState //    MutableStateFlow..asStateFlow()
    
      init {
        viewModelScope.launch {
          _myUiState.value = 1
    //      _myUiState.value = repository.fetchStuff()
          _myUiState.value = 2
        }
      }
    
      constructor(name: String, viewModelScope2: CoroutineScope) : this(viewModelScope2)
    
    
      private val myStateFlow = flow {
        emit(1)
        viewModelScope.launch { }
      }
    
      /**
       *  initialValue 是因为 StateFlow 必须有值
       *  scope 则是用于控制何时开始共享
       *  WhileSubscribed( 5000 )
       *  旋转场景中视图只停止了很短的时间,无论如何都不会超过 5 秒钟,因此 StateFlow 并不会重启
       *  home键  StateFlow时,不会立即停止所有上游数据流,而是会等待一段时间,如果在超时 前 再次收集数据则不会取消上游数据流 ,
       *
       *  FLow 暂停 不超过 5s flow 不停止(重新开始数据继续)  超过 5S 停止  (重新开始数据从头开始)
       */
      private val result: StateFlow<Int> = myStateFlow
        .stateIn(
          initialValue = 1,
          scope = viewModelScope,
          started = WhileSubscribed(5000),
        )
    }
    
    /**
     * SharedFlow   热流    相似     broadCastChannel
     * 一个 flow    只有一个  collect
     * SharedFlow  对应多个  collect   向其所有收集的地方 发出 数据
     * 想要获取数据  就直接    shareFlow collect 即可
     *
     * 发送者 和 观察者
     * 发送者就已经先将消息发出来了,稍后观察者才开始工作,那么此时观察者还应该收到刚才发出的那条消息吗?
     * 此时观察者还能收到消息,那么这种行为就叫做  粘性。收不到之前的消息, 非粘性。
     * EventBus允许我们在使用的时候通过配置指定它是 粘性 的还是 非粘性的。而 LiveData则不允许我们进行指定,它的行为永远都是粘性的。
     *
     * 场景-- 数据的展示  粘性  比较好
     * StateFlow -- 屏幕旋转 Activity 重走  不发射数据  依旧拿到 上次最后数据  是粘性
     *
     * 非粘性  -- SharedFlow
     * 场景 -- 不是数据的展示   登陆成功 value--> 弹框提示 没有跳转  旋转屏幕 Activity 重走 collect 再次执行 拿到value 再次弹框
     */
    suspend fun mySharedFlow() {
      val events = MutableSharedFlow<String>()
      events.emit("1")
      events.collect { println(it) }// 多个地方 collect 都可以得到数据
    }
    /**
     * 数据持续变化  UI 持续刷新  Home  屏幕旋转等  即可暂停UI 又 不间断数据
     * 可以选  repeatOnLifecycle stateFlow WhileSubscribed(5000)    热流  粘性
     *
     * 数据变化完  UI 根据最后一次做提示展示或者跳转等一次性行为  Home 屏幕旋转等,跳转回来  不要复现  提示   数据倒灌=粘性
     * 选  sharedFlow  热流  非粘性
     */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384
    • 385
    • 386
    • 387
    • 388
    • 389
    • 390
    • 391
    • 392
    • 393
    • 394
    • 395
    • 396
    • 397
    • 398
    • 399
    • 400
    • 401
    • 402
    • 403
    • 404
    • 405
    • 406
    • 407
    • 408
    • 409
    • 410
    • 411
    • 412
    • 413
    • 414
    • 415
    • 416
    • 417
    • 418
    • 419
    • 420
    • 421
    • 422
    • 423
    • 424
    • 425
    • 426
    • 427
    • 428
    • 429
    • 430
    • 431
    • 432
    • 433
    • 434
    • 435
    • 436
    • 437
    • 438
    • 439
    • 440
    • 441
    • 442
    • 443
    • 444
    • 445
    • 446
    • 447
    • 448
    • 449
    • 450
    • 451
    • 452
    • 453
    • 454
    • 455
    • 456
    • 457
    • 458
    • 459
    • 460
    • 461
    • 462
    • 463
    • 464
    • 465
    • 466
    • 467
    • 468
    • 469
    • 470
    • 471
    • 472
    • 473
    • 474
    • 475
    • 476
    • 477
    • 478
    • 479
    • 480
    • 481
    • 482
    • 483
    • 484
    • 485
    • 486
    • 487
    • 488
    • 489
    • 490
    • 491
    • 492
    • 493
    • 494
    • 495
    • 496
    • 497
    • 498
    • 499
    • 500
    • 501
    • 502
    • 503
    • 504
    • 505
    • 506
    • 507
    • 508
    • 509
    • 510
    • 511
    • 512
    • 513
    • 514
    • 515
    • 516
    • 517
    • 518
    • 519
    • 520
    • 521
    • 522
    • 523
    • 524
    • 525
    • 526
    • 527
    • 528
    • 529
    • 530
    • 531
    • 532
    • 533
    • 534
    • 535
    • 536
    • 537
    • 538
    • 539
    • 540
    • 541
    • 542
    • 543
    • 544
    • 545
    • 546
    • 547
    • 548
    • 549
    • 550
    • 551
    • 552
    • 553
    • 554
    • 555
    • 556
    • 557
    • 558
    • 559
    • 560
    • 561
    • 562
    • 563
    • 564
    • 565
    • 566
    • 567
    • 568
    • 569
    • 570
    • 571
    • 572
    • 573
    • 574
    • 575
    • 576
    • 577
    • 578
    • 579
    • 580
    • 581
    • 582
    • 583
    • 584
    • 585
    • 586
    • 587
    • 588
    • 589
    • 590
    • 591
    • 592
    • 593
    • 594
    • 595
    • 596
    • 597
    • 598
    • 599
    • 600
    • 601
    • 602
    • 603
    • 604
    • 605
    • 606
    • 607
    • 608
    • 609
    • 610
    • 611
    • 612
    • 613
    • 614
    • 615
    • 616
    • 617
    • 618
    • 619
    • 620
    • 621
    • 622
    • 623
    • 624
    • 625
    • 626
    • 627
    • 628
    • 629
    • 630
    • 631
    • 632
    • 633
    • 634
    • 635
    • 636
    • 637
    • 638
    • 639
  • 相关阅读:
    比特位的计算
    神经网络(NN)
    从零开始搭建Electron项目(二)之例程解析
    windows中nginx配置负载均衡
    Spring Boot 数据库操作Druid和HikariDataSource
    Git 回滚命令笔记
    java_study
    图论问题建模和floodfill算法
    ListableBeanFactory学习
    sql报错注入和联合注入
  • 原文地址:https://blog.csdn.net/u014105655/article/details/127762865