@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 热流 非粘性
*/
Flow,channelFlow--冷流 StateFlow,SharedFlow --热流 粘性 非粘性
于 2022-11-09 09:06:31 首次发布