Flow,channelFlow--冷流 StateFlow,SharedFlow --热流 粘性 非粘性

13 篇文章 0 订阅
1 篇文章 0 订阅
@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<Int>> -> Flow<Int>
* 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  热流  非粘性
 */
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值