简单的Flow
fun main() {
runBlocking {
log("runBlocking")
// 构造flow
val flow = flow {
log("emit")
// 上游
emit("emit")
}
flow.collect{
// 下游
log("collect")
}
}
}
输出:
[Thread[Test worker @coroutine#1,5,main]] runBlocking
[Thread[Test worker @coroutine#1,5,main]] emit
[Thread[Test worker @coroutine#1,5,main]] collect
构造Flow对象,Flow里成员变量为闭包对象(FlowCollector扩展函数)。调用flow的成员变量传入参数为collect的闭包对象(封装了)。
flow调用流程:两个操作符+两个闭包+emit函数。
- collect操作符触发调用,执行力flow的闭包
- flow闭包里调用emit函数,执行了collect闭包
Flow返回集合
把收集到的数据放到list里。
fun main() {
runBlocking {
val result = mutableListOf<String>()
val flow = flow {
emit("emit")
}
flow.collect{
result.add(it)
}
}
}
封装收集:
// 这个其实是官方提供的
public suspend fun <T, C : MutableCollection<in T>> Flow<T>.toCollection(destination: C): C {
collect { value ->
destination.add(value)
}
return destination
}
fun main() {
runBlocking {
val result = mutableListOf<String>()
flow {
emit("emit")
}.toList(result)
}
}
作为Flow的扩展函数。重写了Flow的collect闭包,也就是FlowCollector的emit函数。
Flow变换操作符
transform操作符
fun main() {
runBlocking {
val result = mutableListOf<String>()
flow {
emit("emit")
}.transform {
emit("$it transform")
}.collect{
log("collect:$it")
}
}
}
public inline fun <T, R> Flow<T>.transform(
@BuilderInference crossinline transform: suspend FlowCollector<R>.(value: T) -> Unit
): Flow<R> = flow { // Note: safe flow is used here, because collector is exposed to transform on each operation
collect { value ->
// kludge, without it Unit will be returned and TCE won't kick in, KT-28938
// 上游的数据先经过transform处理
return@collect transform(value)
}
}
- Flow扩展函数,返回一个新的Flow对象
- 新flow对象重写了flow闭包,该闭包调用collect收集了原始Flow的数据
- 经过transform处理,我们再次发射处理过的数据
- 返回的flow的collect闭包被调用
每调用1个transform操作符就会生成一个Flow对象,该对象装饰了它上一个(扩展)对象,flow1装饰flow,flow2装饰flow1。装饰者模式。
fun main() {
runBlocking {
val result = mutableListOf<String>()
flow {
emit("emit")
}.transform {
emit("$it transform1")
}.transform {
emit("$it transform2")
}.collect{
log("collect:$it")
}
}
}
输出:
[Thread[Test worker @coroutine#1,5,main]] collect:emit transform1 transform2
transform还需要自己发射数据,map可自动发射(map内部封装了transform)
fun main() {
runBlocking {
val result = mutableListOf<String>()
flow {
emit("emit")
}.map {
"$it map"
}.collect{
log("collect:$it")
}
}
}
过滤操作符
fun main() {
runBlocking {
flow {
emit("emit1")
emit("emit2")
}.filter {
// 包含emit2字符串才能继续往下发送
it.contains("emit2")
}.collect{
log("collect:$it")
}
}
}
Flow操作符多协程原理以及场景
场景:在主线程执行collect操作,在flow闭包里执行耗时操作
fun main() {
runBlocking {
flow {
// flowOn(Dispatchers.IO)使flow闭包(上游)在子线程执行
log("emit")
emit("emit")
}.flowOn(Dispatchers.IO)
.collect {
log("collect:$it")
}
}
}
输出:
[Thread[DefaultDispatcher-worker-1 @coroutine#2,5,main]] emit
[Thread[Test worker @coroutine#1,5,main]] collect:emit
flow闭包(上游),collect闭包(下游)分别执行在不同的协程以及不同的线程里。
构造了新的协程执行flow闭包,指定了协程分发器为Dispatchers.IO,子线程里执行flow闭包,原理基于ChannelFlow.
Flow处理背压
当上游速度高于下游:
fun main() {
runBlocking {
val time = measureTime {
flow {
log("emit")
emit("emit")
delay(1000)
emit("emit2")
}.collect{
delay(2000)
log("collect $it")
}
}
log("time:$time")
}
}
输出:
[Thread[Test worker @coroutine#1,5,main]] emit
[Thread[Test worker @coroutine#1,5,main]] collect emit
[Thread[Test worker @coroutine#1,5,main]] collect emit2
[Thread[Test worker @coroutine#1,5,main]] time:5.053986s
使用buffer解决背压:
fun main() {
runBlocking {
val time = measureTime {
flow {
log("emit")
emit("emit")
delay(1000)
emit("emit2")
}.buffer().collect{
delay(2000)
log("collect $it")
}
}
log("time:$time")
}
}
输出:
[Thread[Test worker @coroutine#2,5,main]] emit
[Thread[Test worker @coroutine#1,5,main]] collect emit
[Thread[Test worker @coroutine#1,5,main]] collect emit2
[Thread[Test worker @coroutine#1,5,main]] time:4.056571500s
总时间减少了。
构造新的协程执行flow闭包,上游数据会发送到Channel缓冲区,发送完成继续发送下一条collect操作符监听缓冲区是否由数据,若有则收集成功。原理基于ChannelFlow。
上游覆盖旧数据
场景:上游生产速度很快,下游消费速度慢,我们只关心最新数据,旧的数据可以丢掉。使用conflate操作符:
fun main() {
runBlocking {
flow {
repeat(5){
log("emit$it")
emit("emit$it")
delay(100)
}
}.conflate().collect{
delay(500)
log("collect $it")
}
}
}
输出:
[Thread[Test worker @coroutine#2,5,main]] emit0
[Thread[Test worker @coroutine#2,5,main]] emit1
[Thread[Test worker @coroutine#2,5,main]] emit2
[Thread[Test worker @coroutine#2,5,main]] emit3
[Thread[Test worker @coroutine#2,5,main]] emit4
[Thread[Test worker @coroutine#1,5,main]] collect emit0
[Thread[Test worker @coroutine#1,5,main]] collect emit4
中间数据由于下游没有来的及消费,被上游新的数据冲刷掉了
相当于使用了buffer操作符,该buffer只能容纳一个数据,新来的数据将会覆盖旧的数据。原理基于ChannelFlow
Flow变换取最新值
场景:使用transform处理数据,若它处理数据慢,新的数据过来后取消未处理好的值。 使用transformLatest操作符。
fun main() {
runBlocking {
flow {
repeat(5){
// 上游协程1
log("emit$it")
emit("emit$it")
}
}.transformLatest {
delay(200)
// 中间协程2
log("transformLatest $it")
emit("transformLatest $it")
}.collect{
// 下游协程3
log("collect $it")
}
}
}
协程:
[Thread[Test worker @coroutine#2,5,main]] emit0
[Thread[Test worker @coroutine#2,5,main]] emit1
[Thread[Test worker @coroutine#2,5,main]] emit2
[Thread[Test worker @coroutine#2,5,main]] emit3
[Thread[Test worker @coroutine#2,5,main]] emit4
[Thread[Test worker @coroutine#7,5,main]] transformLatest emit4
[Thread[Test worker @coroutine#1,5,main]] collect transformLatest emit4
由于transform处理速度比较慢,上游有新数据过来后会取消transform未处理的数据
override suspend fun flowCollect(collector: FlowCollector<R>) {
coroutineScope {
var previousFlow: Job? = null
//开始收集上游数据
flow.collect { value ->
previousFlow?.apply {
//若是之前的协程还在,则取消
cancel(ChildCancelledException())
join()
}
//开启协程执行,此处选择不分发新线程
previousFlow = launch(start = CoroutineStart.UNDISPATCHED) {
collector.transform(value)
}
}
}
}
构造新的协程执行flow闭包,收集到数据后再开启协程3,在协程里会调用transformLatest的闭包,最终调用collect闭包。协程1继续发送数据,若发现协程2还在运行,则取消协程2。原理基于ChannelFlow
map也有类似操作。
fun main() {
runBlocking {
flow {
repeat(5){
log("emit$it")
emit("emit$it")
}
}.mapLatest {
delay(200)
// 中间协程2
log("transformLatest $it")
"transformLatest $it"
}.collect{
// 下游协程3
log("collect $it")
}
}
}
输出:
[Thread[Test worker @coroutine#2,5,main]] emit0
[Thread[Test worker @coroutine#2,5,main]] emit1
[Thread[Test worker @coroutine#2,5,main]] emit2
[Thread[Test worker @coroutine#2,5,main]] emit3
[Thread[Test worker @coroutine#2,5,main]] emit4
[Thread[Test worker @coroutine#7,5,main]] transformLatest emit4
[Thread[Test worker @coroutine#1,5,main]] collect transformLatest emit4
收集最新数据
场景:监听下载进度,UI显示最新进度。没必要频繁刷新UI,可以忽略旧数据。 使用collectLatest操作符:
fun main() {
runBlocking {
val time = measureTime {
val flow1 = flow {
repeat(100) {
emit(it)
}
}
flow1.collectLatest {
delay(20)
log("collectLatest:$it")
}
}
log("time:$time")
}
}
输出:
[Thread[Test worker @coroutine#102,5,main]] collectLatest:99
[Thread[Test worker @coroutine#1,5,main]] time:120.534600ms
开启新的协程flow闭包,若collect收集比较慢,下个数据emit过来会取消未处理的数据。原理基于ChannelFlow
多Flow操作符里的多协程原理以及使用场景
展平流
flatMapConcat
场景:
- 请求学生信息,使用flow1
- 请求该学生班主任信息,用flow2
- 先拿到学生信息,通过信息里带的班主任id取拿班主任信息
串行有前后依关系。使用flatMapConcat操作符:
fun main() {
runBlocking {
val flow1 = flow {
emit("stuInfo")
}
flow1.flatMapConcat {
// flow2
flow {
emit("$it teachInfo")
}
}.collect{
log("collect:$it")
}
}
}
输出:
[Thread[Test worker @coroutine#1,5,main]] collect:stuInfo teachInfo
展平,将两个flow数据拍平了输出
可以请求多个学生班主任信息:
fun main() {
runBlocking {
val time = measureTime {
val flow1 = flow {
log("flow1 emit")
emit("stuInfo 1")
emit("stuInfo 2")
emit("stuInfo 3")
}
flow1.flatMapConcat {
// flow2
flow {
log("flatMapConcat: $it teachInfo")
emit("$it teachInfo")
delay(1000)
}
}.collect {
log("collect:$it")
}
}
log("time:$time")
}
}
输出:
[Thread[Test worker @coroutine#1,5,main]] flow1 emit
[Thread[Test worker @coroutine#1,5,main]] flatMapConcat: stuInfo 1 teachInfo
[Thread[Test worker @coroutine#1,5,main]] collect:stuInfo 1 teachInfo
[Thread[Test worker @coroutine#1,5,main]] flatMapConcat: stuInfo 2 teachInfo
[Thread[Test worker @coroutine#1,5,main]] collect:stuInfo 2 teachInfo
[Thread[Test worker @coroutine#1,5,main]] flatMapConcat: stuInfo 3 teachInfo
[Thread[Test worker @coroutine#1,5,main]] collect:stuInfo 3 teachInfo
[Thread[Test worker @coroutine#1,5,main]] time:3032
不涉及多协程,使用装饰模式将flow2使用map变换,将flow1、flow2数据发射出来,Concat将两个flow链接起来。
flatMapMerge
场景:同时查看多个学生班主任信息
fun main() {
runBlocking {
val time = measureTime {
val flow1 = flow {
log("flow1 emit")
emit("stuInfo 1")
emit("stuInfo 2")
emit("stuInfo 3")
}
flow1.flatMapMerge(4) {
// flow2
flow {
log("flatMapMerge: $it teachInfo")
emit("$it teachInfo")
delay(1000)
}
}.collect {
log("collect:$it")
}
}
log("time:$time")
}
}
输出:
[Thread[Test worker @coroutine#2,5,main]] flow1 emit
[Thread[Test worker @coroutine#3,5,main]] flatMapMerge: stuInfo 1 teachInfo
[Thread[Test worker @coroutine#4,5,main]] flatMapMerge: stuInfo 2 teachInfo
[Thread[Test worker @coroutine#5,5,main]] flatMapMerge: stuInfo 3 teachInfo
[Thread[Test worker @coroutine#1,5,main]] collect:stuInfo 1 teachInfo
[Thread[Test worker @coroutine#1,5,main]] collect:stuInfo 2 teachInfo
[Thread[Test worker @coroutine#1,5,main]] collect:stuInfo 3 teachInfo
[Thread[Test worker @coroutine#1,5,main]] time:1.073181s
flatMapMerge是并发执行,速度比flatMapConcat快。flatMapMerge可以指定并发数量,指定flatMapMerge(0)。flatMapMerge退化为flatMapConcat
override suspend fun collectTo(scope: ProducerScope<T>) {
val semaphore = Semaphore(concurrency)
val collector = SendingCollector(scope)
val job: Job? = coroutineContext[Job]
flow.collect { inner ->
job?.ensureActive()
//并发数限制锁
semaphore.acquire()
scope.launch {
//开启新的协程
try {
//执行flatMapMerge闭包里的flow
inner.collect(collector)
} finally {
semaphore.release() // Release concurrency permit
}
}
}
}
flow1里的每个学生会触发班主任信息flow2。新来了协程去执行flow2里的闭包。原理基于ChannelFlow
flatMpaLatest
场景:flatMapConcat是线性执行的,可以使用flatMapMerge提升效率。为了节省资源,请求班主任信息时,若某个学生班主任信息没有返回,而下一个学生班主任信息已经开始请求,则取消上一个没有返回的班主任Flow。使用flatMapLatest
fun main() {
runBlocking {
val time = measureTime {
val flow1 = flow {
emit("stuInfo 1")
emit("stuInfo 2")
emit("stuInfo 3")
}
flow1.flatMapLatest {
// flow2
flow {
delay(1000)
emit("$it teachInfo")
}
}.collect {
log("collect:$it")
}
}
log("time:$time")
}
}
输出:
[Thread[Test worker @coroutine#1,5,main]] collect:stuInfo 3 teachInfo
[Thread[Test worker @coroutine#1,5,main]] time:1.082095300s
和transformLatest相似。原理基于ChannelFlow。 transformLatest、mapLatest、collectLatest、flapMapLatest核心实现都是ChannelFlowTransformLatest,最终继承自:ChannelFlow
组合流
combine。场景:查询学生的性别以及选修了某个课程。 查询性别与选修课程可以同时发出请求:
fun main() {
runBlocking {
val time = measureTime {
val flow1 = flow {
emit("stuSex 1")
emit("stuSex 2")
emit("stuSex 3")
}
val flow2 = flow {
emit("stuSubject")
}
flow1.combine(flow2){
sex,subject->"sex:$sex subject:$subject"
}.collect{
log(it)
}
}
log("time:$time")
}
}
输出:
[Thread[Test worker @coroutine#1,5,main]] sex:stuSex 1 subject:stuSubject
[Thread[Test worker @coroutine#1,5,main]] sex:stuSex 2 subject:stuSubject
[Thread[Test worker @coroutine#1,5,main]] sex:stuSex 3 subject:stuSubject
[Thread[Test worker @coroutine#1,5,main]] time:83.032900ms
flow1的每个emit和flow2的emit关联了起来。 combine特点:短的一方会等待长的一方结束后才结束。
fun main() {
runBlocking {
val time = measureTime {
val flow1 = flow {
emit("a")
emit("b")
emit("c")
emit("d")
}
val flow2 = flow {
emit("1")
emit("2")
}
flow1.combine(flow2){
sex,subject->"sex:$sex subject:$subject"
}.collect{
log(it)
}
}
log("time:$time")
}
}
输出:
[Thread[Test worker @coroutine#1,5,main]] sex:a subject:1
[Thread[Test worker @coroutine#1,5,main]] sex:b subject:2
[Thread[Test worker @coroutine#1,5,main]] sex:c subject:2
[Thread[Test worker @coroutine#1,5,main]] sex:d subject:2
[Thread[Test worker @coroutine#1,5,main]] time:49.213100ms
flow2早就发射了2,会一直等到flow1发射结束。
zip
在combine基础上,无论是学生性别还是学生课程,只要某个flow结束了就取消flow。 使用zip操作符:
fun main() {
runBlocking {
val time = measureTime {
val flow1 = flow {
emit("a")
emit("b")
emit("c")
emit("d")
}
val flow2 = flow {
emit("1")
emit("2")
}
flow1.zip(flow2){
sex,subject->"sex:$sex subject:$subject"
}.collect{
log(it)
}
}
log("time:$time")
}
}
输出:
[Thread[Test worker @coroutine#1,5,main]] sex:a subject:1
[Thread[Test worker @coroutine#1,5,main]] sex:b subject:2
[Thread[Test worker @coroutine#1,5,main]] time:36.900100ms
flow2结束了,flow1没发送完成。
短的flow结束,另一个flow也结束。
Flow操作符
fiter map flowOn buffer callbackFlow flatMapxx以及comnine zip