這一次蒙揣,讓Kotlin Flow 操作符真正好用起來

前言

Kotlin Flow 如此受歡迎大部分歸功于其豐富靶溜、簡潔的操作符,巧妙使用Flow操作符可以大大簡化我們的程序結(jié)構(gòu)懒震,提升可讀性與可維護性罩息。
然而,雖然好用个扰,但有些操作符不太好理解瓷炮,可惜的是網(wǎng)上大部分文章只是簡單介紹其使用,并沒有梳理各個操作符的關(guān)系以及引入的緣由递宅,本篇將通過關(guān)鍵原理與使用場景串聯(lián)大部分操作符娘香,以期達到舉一反三的效果。
通過本篇文章办龄,你將了解到:

  1. 操作符全家福
  2. 單Flow操作符的原理以及使用場景
  3. 單Flow操作符里的多協(xié)程原理以及使用場景
  4. 多Flow操作符里的多協(xié)程原理以及使用場景
  5. Flow操作符該怎么學(xué)烘绽?

1. 操作符全家福

Flow操作符分類.png

紅色部分為使用了多協(xié)程的操作符
上圖僅包含常用官方提供的操作符,其它未包含進來的操作符原理也是類似的俐填,當(dāng)然我們也可以封裝自己的操作符

由圖上可知安接,將操作符分為了三類:

  1. 構(gòu)建操作符
  2. 中間操作符
  3. 末端操作符

2. 單Flow操作符的原理以及使用場景

最簡單的Flow

    fun test0() {
        runBlocking {
            //構(gòu)造flow
            val flow = flow {
                //上游
                emit("hello world ${Thread.currentThread()}")
            }
            //收集flow
            flow.collect {
                //下游
                println("collect:$it ${Thread.currentThread()}")
            }
        }
    }

如上包含了兩種操作符:構(gòu)造操作符flow與末端操作符collect。


image.png

總結(jié)來說英融,flow調(diào)用流程簡化為:兩個操作符+兩個閉包+emit函數(shù):

  1. collect操作符觸發(fā)調(diào)用盏檐,執(zhí)行了flow的閉包
  2. flow閉包里調(diào)用emit函數(shù),執(zhí)行了collect閉包

Flow返回集合

collect閉包里僅僅只是打印了數(shù)據(jù)驶悟,有個需求:需要將收集到的數(shù)據(jù)放在List里胡野。
很容易就想到:

    fun test00() {
        runBlocking {
            val result = mutableListOf<String>()
            //構(gòu)造flow
            val flow = flow {
                //上游
                emit("hello world ${Thread.currentThread()}")
            }
            //收集flow
            flow.collect {
                //下游
                println("collect:$it ${Thread.currentThread()}")
                result.add(it)
            }
        }
    }

如上,定義List變量撩银,在collect的閉包里收到數(shù)據(jù)后填充到List里给涕。
某天豺憔,我們發(fā)現(xiàn)這個功能挺常用额获,需要將它封裝起來够庙,外界只需要傳入List對象即可。

public suspend fun <T, C : MutableCollection<in T>> Flow<T>.toCollection(destination: C): C {
    collect { value ->
        destination.add(value)
    }
    return destination
}

外部使用:

    fun test01() {
        runBlocking {
            val result = mutableListOf<String>()
            flow {
                //上游
                emit("hello world ${Thread.currentThread()}")
            }.toList(result)
        }
    }

如此一看抄邀,簡單了許多耘眨,這也是官方提供的Flow操作符。

原理很簡單:

  1. 作為Flow的擴展函數(shù)
  2. 重寫了Flow的collect閉包境肾,也就是FlowCollector的emit函數(shù)

后續(xù)很多操作符都是這么個套路剔难,比如取Flow的第一個數(shù)據(jù):first操作符,比如取對Flow里相鄰的兩個值做操作:reduce操作符等等奥喻。

Flow變換操作符

有個需求:在Flow流到下游之前偶宫,對數(shù)據(jù)進行處理,處理完成后再發(fā)射出去环鲤。
可以使用transform 操作符纯趋。

    fun test02() {
        runBlocking {
            flow {
                //上游
                emit("hello world ${Thread.currentThread()}")
            }.transform {
                emit("$it man")
            }.collect {
                println("$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 ->
        //上游的數(shù)據(jù)先經(jīng)過transform處理
        return@collect transform(value)
    }
}
  1. 依然是Flow擴展函數(shù),返回一個新的Flow對象
  2. 新Flow對象重寫了flow閉包冷离,該閉包里調(diào)用collect收集了原始Flow的數(shù)據(jù)
  3. 當(dāng)數(shù)據(jù)到來后吵冒,經(jīng)過transform處理,而我們自定義的transform閉包里將數(shù)據(jù)再次發(fā)射出去
  4. 最后新返回的flow的collect閉包被調(diào)用

上面只是使用了一個transform操作符西剥,若是多個transform操作符痹栖,該怎么去分析呢?其實瞭空,套路是有跡可循的揪阿。
這里涉及到了一種設(shè)計模式:裝飾者模式


image.png

每調(diào)用1個transform操作符就會新生成一個Flow對象,該對象裝飾了它的上一個(擴展)對象匙铡,如上Flow1裝飾原始Flow图甜,F(xiàn)low2裝飾Flow1。

    fun test02() {
        runBlocking {
            flow {
                //上游
                emit("hello world ${Thread.currentThread()}")
            }.transform {
                emit("$it 1")
            }.transform {
                emit("$it 2")
            }.transform {
                emit("$it 3")
            }.collect {
                println("$it")
            }
        }
    }

如上鳖眼,相信你很快就知道輸出結(jié)果了黑毅。

你可能覺得transform還需要自己發(fā)射數(shù)據(jù),有點麻煩钦讳,map可解君憂矿瘦。

    fun test03() {
        runBlocking {
            flow {
                //上游
                emit("hello world ${Thread.currentThread()}")
            }.map {
                "$it 1"
            }.collect {
                println("$it")
            }
        }
    }

map內(nèi)部封裝了transform。

過濾操作符

有個需求:對上流的數(shù)據(jù)進行某種條件的篩選過濾愿卒。
有了transform的經(jīng)驗缚去,我們很容易想到定義擴展函數(shù)返回新的Flow,并重寫collect的閉包琼开,在閉包里進行限制易结。

public inline fun <T> Flow<T>.filter(crossinline predicate: suspend (T) -> Boolean): Flow<T> = transform { value ->
    //條件滿足再發(fā)射
    if (predicate(value)) return@transform emit(value)
}

internal inline fun <T, R> Flow<T>.unsafeTransform(
    @BuilderInference crossinline transform: suspend FlowCollector<R>.(value: T) -> Unit
): Flow<R> = unsafeFlow { // Note: unsafe flow is used here, because unsafeTransform is only for internal use
    collect { value ->
        return@collect transform(value)
    }
}

使用方式:

    fun test04() {
        runBlocking {
            flow {
                //上游
                emit("hello world ${Thread.currentThread()}")
                emit("fish")
            }.filter {
                //包含hello字符串才繼續(xù)往下發(fā)送
                it.contains("hello")
            }.collect {
                println("$it")
            }
        }
    }

掌握了以上套路,再去理解其它類似的操作符就很簡單了,都是一些簡單的變種搞动。

3. 單Flow操作符里的多協(xié)程原理以及使用場景

Flow里如何切換協(xié)程與線程

上面提到的操作符躏精,如map、filter鹦肿,相信大家也看出來了:

整個流程的過程沒有涉及到其它協(xié)程矗烛,也沒有涉及到其它的線程,是比較單純也比較容易理解

有個需求:在主線程執(zhí)行collect操作符箩溃,在flow閉包里執(zhí)行耗時操作瞭吃。
此時我們就需要flow閉包里的代碼在子線程執(zhí)行。
你可能一下子就說出了答案:使用flowOn操作符涣旨。

    fun test05() {
        runBlocking {
            flow {
                //上游
                println("emit ${Thread.currentThread()}")
                emit("hello world")
            }.flowOn(Dispatchers.IO)//flowOn 之前的操作符在新協(xié)程里執(zhí)行
                .collect {
                    println("$it")
                    println("collect ${Thread.currentThread()}")
                }
        }
    }
//打印結(jié)果
emit Thread[DefaultDispatcher-worker-1 @coroutine#3,5,main]
hello world
collect Thread[main @coroutine#2,5,main]

可以看出歪架,flow閉包(上游),collect閉包(下游)分別執(zhí)行在不同的協(xié)程以及不同的線程里霹陡。
flowOn原理簡單來說:

構(gòu)造了新的協(xié)程執(zhí)行flow閉包牡拇,又因為指定了協(xié)程分發(fā)器為Dispatchers.IO,因此會在子線程里執(zhí)行flow閉包
原理是基于ChannelFlow

Flow處理背壓

有個需求:上游發(fā)射數(shù)據(jù)速度高于下游穆律,如何提升發(fā)射效率惠呼?
如下:

    fun test06() {
        runBlocking {
            val time = measureTimeMillis {
                flow {
                    //上游
                    println("emit ${Thread.currentThread()}")
                    emit("hello world")
                    delay(1000)
                    emit("hello world2")
                }.collect {
                        delay(2000)
                        println("$it")
                        println("collect ${Thread.currentThread()}")
                    }
            }
            println("use time:$time")
        }
    }
//打印
emit Thread[main @coroutine#2,5,main]
hello world
collect Thread[main @coroutine#2,5,main]
hello world2
collect Thread[main @coroutine#2,5,main]
use time:5024

使用buffer操作符解決背壓問題:

    fun test06() {
        runBlocking {
            val time = measureTimeMillis {
                flow {
                    //上游
                    println("emit ${Thread.currentThread()}")
                    emit("hello world")
                    delay(1000)
                    emit("hello world2")
                }.buffer().collect {
                        delay(2000)
                        println("$it")
                        println("collect ${Thread.currentThread()}")
                    }
            }
            println("use time:$time")
        }
    }
//打印結(jié)果
emit Thread[main @coroutine#3,5,main]
hello world
collect Thread[main @coroutine#2,5,main]
hello world2
collect Thread[main @coroutine#2,5,main]
use time:4065

可以看出,總耗時減少了峦耘。
buffer原理簡單來說:

構(gòu)造了新的協(xié)程執(zhí)行flow閉包剔蹋,上游數(shù)據(jù)會發(fā)送到Channel 緩沖區(qū)里,發(fā)送完成繼續(xù)發(fā)送下一條
collect操作符監(jiān)聽緩沖區(qū)是否有數(shù)據(jù)辅髓,若有則收集成功
原理是基于ChannelFlow

關(guān)于flowOn和buffer更詳細(xì)的原理請移步:Kotlin Flow 背壓和線程切換竟然如此相似

上游覆蓋舊數(shù)據(jù)

有個需求:上游生產(chǎn)速度很快泣崩,下游消費速度慢,我們只關(guān)心最新數(shù)據(jù)洛口,舊的數(shù)據(jù)沒價值可以丟掉矫付。
使用conflate操作符處理:

    fun test07() {
        runBlocking {
            flow {
                //上游
                repeat(5) {
                    emit("emit $it")
                    delay(100)
                }
            }.conflate().collect {
                delay(500)
                println("$it")
            }
        }
    }
//打印結(jié)果:
emit 0
emit 4

可以看出,中間產(chǎn)生的數(shù)據(jù)由于下游沒有來得及消費第焰,被上游新的數(shù)據(jù)沖刷掉了买优。

conflate原理簡單來說:

相當(dāng)于使用了buffer操作符,該buffer只能容納一個數(shù)據(jù)挺举,新來的數(shù)據(jù)將會覆蓋舊的數(shù)據(jù)
原理是基于ChannelFlow

Flow變換取最新值

有個需求:在使用transform處理數(shù)據(jù)的時候杀赢,若是它處理比較慢,當(dāng)有新的值過來后就取消未處理好的值湘纵。
使用transformLatest操作符處理:

    fun test08() {
        runBlocking {
            flow {
                //上游脂崔,協(xié)程1
                repeat(5) {
                    emit("emit $it")
                }
                println("emit ${Thread.currentThread()}")
            }.transformLatest {
                //協(xié)程2
                delay(200)
                emit("$it fish")
            }.collect {
                println("collect ${Thread.currentThread()}")
                println("$it")
            }
        }
    }
打印結(jié)果:
emit Thread[main @coroutine#3,5,main]
collect Thread[main @coroutine#2,5,main]
emit 4 fish

可以看出,由于transform處理速度比較慢梧喷,上游有新的數(shù)據(jù)過來后會取消transform里未處理的數(shù)據(jù)砌左。
查看源碼是如何處理的:

override suspend fun flowCollect(collector: FlowCollector<R>) {
    coroutineScope {
        var previousFlow: Job? = null
        //開始收集上游數(shù)據(jù)
        flow.collect { value ->
            previousFlow?.apply {
                //若是之前的協(xié)程還在脖咐,則取消
                cancel(ChildCancelledException())
                join()
            }
            //開啟協(xié)程執(zhí)行,此處選擇不分發(fā)新線程
            previousFlow = launch(start = CoroutineStart.UNDISPATCHED) {
                collector.transform(value)
            }
        }
    }
}

transformLatest原理簡單來說:

構(gòu)造新的協(xié)程1執(zhí)行flow閉包汇歹,收集到數(shù)據(jù)后再開啟新的協(xié)程2文搂,在協(xié)程里會調(diào)用transformLatest的閉包,最終調(diào)用collect的閉包
協(xié)程1繼續(xù)發(fā)送數(shù)據(jù)秤朗,若是發(fā)現(xiàn)協(xié)程2還在運行,則取消協(xié)程2
原理是基于ChannelFlow

同理笔喉,map也有類似的操作符:

    fun test09() {
        runBlocking {
            flow {
                //上游
                repeat(5) {
                    emit("emit $it")
                }
                println("emit ${Thread.currentThread()}")
            }.mapLatest {
                delay(200)
                "$it fish"
            }.collect {
                println("collect ${Thread.currentThread()}")
                println("$it")
            }
        }
    }
//打印結(jié)果
emit Thread[main @coroutine#3,5,main]
collect Thread[main @coroutine#2,5,main]
emit 4 fish

收集最新的數(shù)據(jù)

有個需求:監(jiān)聽下載進度取视,UI展示最新進度。
分析:此種場景下常挚,我們只是關(guān)注最新的進度作谭,沒必要頻繁刷新UI,因此使用Flow實現(xiàn)時上游發(fā)射太快了可以忽略舊的數(shù)據(jù)奄毡。
使用collectLatest操作符實現(xiàn):

    fun test014() {
        runBlocking {
            val time = measureTimeMillis {
                val flow1 = flow {
                    repeat(100) {
                        emit(it + 1)
                    }
                }
                flow1.collectLatest {
                    delay(20)
                    println("collect progress $it")
                }
            }
            println("use time:$time")
        }
    }
//打印結(jié)果
collect progress 100
use time:169

collectLatest原理簡單來說:

開啟新協(xié)程執(zhí)行flow閉包
若是collect收集比較慢折欠,下一個數(shù)據(jù)emit過來后會取消未處理的數(shù)據(jù)
原理是基于ChannelFlow

4. 多Flow操作符里的多協(xié)程原理以及使用場景

很多時候我們不止操作單個Flow,有可能需要結(jié)合多個Flow來實現(xiàn)特定的業(yè)務(wù)場景吼过。

展平流

flatMapConcat

有個需求:請求某個學(xué)生的班主任信息锐秦,這里涉及到兩個接口:

  1. 請求學(xué)生信息,使用Flow1表示
  2. 請求該學(xué)生的班主任信息盗忱,使用Flow2表示
  3. 我們需要先拿到學(xué)生的信息酱床,通過信息里帶的班主任id去請求班主任信息

分析需求可知:獲取學(xué)生信息的請求和獲取班主任信息的請求是串行的,有前后依賴關(guān)系趟佃。
使用flatMapConcat操作符實現(xiàn):

    fun test010() {
        runBlocking {
            val flow1 = flow {
                emit("stuInfo")
            }
            flow1.flatMapConcat {
                //flow2
                flow {
                    emit("$it teachInfo")
                }
            }.collect {
                println("collect $it")
            }
        }
    }
//打印結(jié)果:
collect stuInfo teachInfo

從打印結(jié)果可以看出:

所謂展平扇谣,實際上就是將兩個Flow的數(shù)據(jù)拍平了輸出

當(dāng)然,你也可以請求多個學(xué)生的班主任信息:

    fun test011() {
        runBlocking {
            val time = measureTimeMillis {
                val flow1 = flow {
                    println("emit ${Thread.currentThread()}")
                    emit("stuInfo 1")
                    emit("stuInfo 2")
                    emit("stuInfo 3")
                }
                flow1.flatMapConcat {
                    //flow2
                    flow {
                        println("flatMapConcat ${Thread.currentThread()}")
                        emit("$it teachInfo")
                        delay(1000)
                    }
                }.collect {
                    println("collect ${Thread.currentThread()}")
                    println("collect $it")
                }
            }
            println("use time:$time")
        }
    }
//打印結(jié)果:
emit Thread[main @coroutine#2,5,main]
flatMapConcat Thread[main @coroutine#2,5,main]
collect Thread[main @coroutine#2,5,main]
collect stuInfo 1 teachInfo
flatMapConcat Thread[main @coroutine#2,5,main]
collect Thread[main @coroutine#2,5,main]
collect stuInfo 2 teachInfo
flatMapConcat Thread[main @coroutine#2,5,main]
collect Thread[main @coroutine#2,5,main]
collect stuInfo 3 teachInfo
use time:3032

flatMapConcat原理簡單來說:

flatMapConcat 并沒有涉及到多協(xié)程闲昭,使用了裝飾者模式
先將Flow2使用map進行變換罐寨,而后將Flow1、Flow2數(shù)據(jù)發(fā)射出來
Concat顧名思義序矩,將兩個Flow連接起來

flatMapMerge

有個需求:在flatMapConcat里鸯绿,先查詢了學(xué)生1的班主任信息后才會查詢學(xué)生2的班主任信息,依照此順序進行查詢◆さ恚現(xiàn)在需要提升效率楞慈,同時查詢多個多個學(xué)生的班主任信息。
使用flatMapMerge操作符實現(xiàn):

    fun test012() {
        runBlocking {
            val time = measureTimeMillis {
                val flow1 = flow {
                    println("emit ${Thread.currentThread()}")
                    emit("stuInfo 1")
                    emit("stuInfo 2")
                    emit("stuInfo 3")
                }
                flow1.flatMapMerge(4) {
                    //flow2
                    flow {
                        println("flatMapMerge ${Thread.currentThread()}")
                        emit("$it teachInfo")
                        delay(1000)
                    }
                }.collect {
                    println("collect ${Thread.currentThread()}")
                    println("collect $it")
                }
            }
            println("use time:$time")
        }
    }
//打印結(jié)果:
flatMapMerge Thread[main @coroutine#6,5,main]
collect Thread[main @coroutine#2,5,main]
collect stuInfo 1 teachInfo
collect Thread[main @coroutine#2,5,main]
collect stuInfo 2 teachInfo
collect Thread[main @coroutine#2,5,main]
collect stuInfo 3 teachInfo
use time:1086

可以看出啃擦,flatMapMerge由于是并發(fā)執(zhí)行囊蓝,整體速度比flatMapConcat快了很多。
flatMapMerge可以指定并發(fā)的數(shù)量令蛉,當(dāng)指定flatMapMerge(0)時聚霜,flatMapMerge退化為flatMapConcat狡恬。
關(guān)鍵源碼如下:

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()
        //并發(fā)數(shù)限制鎖
        semaphore.acquire()
        scope.launch {
            //開啟新的協(xié)程
            try {
                //執(zhí)行flatMapMerge閉包里的flow
                inner.collect(collector)
            } finally {
                semaphore.release() // Release concurrency permit
            }
        }
    }
}

flatMapMerge原理簡單來說:

flow1里的每個學(xué)生信息會觸發(fā)去獲取班主任信息flow2
新開了協(xié)程去執(zhí)行flow2的閉包
原理是基于ChannelFlow

flatMapLatest

有個需求:flatMapConcat 是線性執(zhí)行的,可以使用flatMapMerge提升效率蝎宇。為了節(jié)約資源弟劲,在請求班主任信息的時候,若是某個學(xué)生的班主任信息沒有返回姥芥,而下一個學(xué)生的班主任信息已經(jīng)開始請求兔乞,則取消上一個沒有返回的班主任Flow。
使用flatMapLatest操作符實現(xiàn):

    fun test013() {
        runBlocking {
            val time = measureTimeMillis {
                val flow1 = flow {
//                    println("emit ${Thread.currentThread()}")
                    emit("stuInfo 1")
                    emit("stuInfo 2")
                    emit("stuInfo 3")
                }
                flow1.flatMapLatest {
                    //flow2
                    flow {
//                        println("flatMapLatest ${Thread.currentThread()}")
                        delay(1000)
                        emit("$it teachInfo")
                    }
                }.collect {
//                    println("collect ${Thread.currentThread()}")
                    println("collect $it")
                }
            }
            println("use time:$time")
        }
    }
//打印結(jié)果:
collect stuInfo 3 teachInfo
use time:1105

可以看出凉唐,只有學(xué)生3的班主任信息打印出來了庸追,并且整體時間都減少了。
flatMapLatest原理簡單來說:

和transformLatest很相似
原理是基于ChannelFlow

簡單總結(jié)一下關(guān)于收集最新數(shù)據(jù)的操作符:

transformLatest台囱、mapLatest淡溯、collectLatest、flatMapLatest 四者的核心實現(xiàn)都是ChannelFlowTransformLatest簿训,而它最終繼承自:ChannelFlow

組合流

combine

有個需求:查詢學(xué)生的性別以及選修了某個課程咱娶。
分析:涉及到兩個需求,查詢學(xué)生性別與查詢選修課程强品,輸出結(jié)果是:性別:xx膘侮,選修了:xx課程。這倆請求可以同時發(fā)出的榛,并沒有先后順序喻喳,因此我們沒必要使用flatMapXX系列操作符。
使用combine操作符:

    fun test015() {
        runBlocking {
            val time = measureTimeMillis {
                val flow1 = flow {
                    emit("stuSex 1")
                    emit("stuSex 2")
                    emit("stuSex 3")
                }
                val flow2 = flow {
                    emit("stuSubject")
                }
                flow1.combine(flow2) {
                    sex, subject->"$sex-->$subject"
                }.collect {
                    println(it)
                }
            }
            println("use time:$time")
        }
    }
//打印結(jié)果:
stuSex 1-->stuSubject
stuSex 2-->stuSubject
stuSex 3-->stuSubject
use time:46

可以看出困曙,flow1的每個emit和flow2的emit關(guān)聯(lián)起來了表伦。
combine操作符有個特點:

短的一方會等待長的一方結(jié)束后才結(jié)束

看個例子就比較清晰:

    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")
        }
    }
//打印結(jié)果
a-->1
b-->2
c-->2
d-->2
use time:45

flow2早就發(fā)射到"2"了,會一直等到flow1發(fā)射結(jié)束慷丽。

combine原理簡單來說:


image.png

zip

在combine需求的基礎(chǔ)上蹦哼,我們又有個優(yōu)化:無論是學(xué)生性別還是學(xué)生課程,只要某個Flow獲取結(jié)束了就取消Flow要糊。
使用zip操作符:

    fun test017() {
        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")
        }
    }
//打印結(jié)果
a-->1
b-->2
use time:71

可以看出flow2先結(jié)束了纲熏,并且flow1沒發(fā)送完成。
zip原理簡單來說:


image.png

可以看出锄俄,zip的特點:

短的Flow結(jié)束局劲,另一個Flow也結(jié)束

5. Flow操作符該怎么學(xué)?

以上我們由淺入深分別分析了:

  1. 單個Flow操作符原理與使用場景
  2. 單個Flow操作符切換多個協(xié)程的原理與使用場景
  3. 多個Flow操作符切換多個協(xié)程的原理與使用場景

以上三者是遞進關(guān)系奶赠,第1點比較簡單鱼填,第2點難度適中。
尤其是第3點比較難以理解毅戈,因為涉及到了其它的知識:Channel苹丸、ChannelFlow愤惰、多協(xié)程、線程切換等赘理。
在之前的文章中有提到過:ChannelFlow是Flow復(fù)雜操作符的基礎(chǔ)宦言,想要掌握復(fù)雜操作符的原理需要明白ChannelFlow的運行機制,有興趣可移步:當(dāng)商模,Kotlin Flow與Channel相逢

建議Flow操作符學(xué)習(xí)步驟:

  1. 先會使用簡單的操作符filter奠旺、map等
  2. 再學(xué)會使用flowOn、buffer施流、callbackFlow等操作符
  3. 進而使用flatMapXXX以及combine响疚、zip等操作符
  4. 最后可以看看其實現(xiàn)原理,達到舉一反三應(yīng)用到實際需求里

Flow操作符的閉坑指南:

  1. 涉及到多協(xié)程的操作符嫂沉,需要關(guān)注其執(zhí)行的線程環(huán)境
  2. 涉及到多協(xié)程的操作符,需要關(guān)注協(xié)程的生命周期

說實話扮碧,F(xiàn)low操作符要掌握好挺難的趟章,它幾乎涉及了協(xié)程所有的知識點,也是協(xié)程實際應(yīng)用的精華慎王。這篇是我在協(xié)程系列里花費時間最長的文章了(也許也是最后一篇了)蚓土,即使自己弄明白了,怎樣把它很自然地遞進引出也是個有挑戰(zhàn)的事赖淤。
若你能夠在本篇的分析中得到一點啟發(fā)蜀漆,那說明我的分享是有價值的。
由于篇幅關(guān)系咱旱,一些操作符debounce确丢、sample等并沒有分析,也沒有再貼flatMapXXX的源碼細(xì)節(jié)(這部分之前的文章都有分析過)吐限,若你有需要可以給我留言評論鲜侥。

本文基于Kotlin 1.6.1,覆蓋所有Flow操作符的demo

您若喜歡诸典,請點贊描函、關(guān)注、收藏狐粱,您的鼓勵是我前進的動力

持續(xù)更新中舀寓,和我一起步步為營系統(tǒng)、深入學(xué)習(xí)Android/Kotlin

1肌蜻、Android各種Context的前世今生
2互墓、Android DecorView 必知必會
3、Window/WindowManager 不可不知之事
4蒋搜、View Measure/Layout/Draw 真明白了
5轰豆、Android事件分發(fā)全套服務(wù)
6胰伍、Android invalidate/postInvalidate/requestLayout 徹底厘清
7、Android Window 如何確定大小/onMeasure()多次執(zhí)行原因
8酸休、Android事件驅(qū)動Handler-Message-Looper解析
9骂租、Android 鍵盤一招搞定
10、Android 各種坐標(biāo)徹底明了
11斑司、Android Activity/Window/View 的background
12渗饮、Android Activity創(chuàng)建到View的顯示過
13、Android IPC 系列
14宿刮、Android 存儲系列
15互站、Java 并發(fā)系列不再疑惑
16、Java 線程池系列
17僵缺、Android Jetpack 前置基礎(chǔ)系列
18胡桃、Android Jetpack 易懂易學(xué)系列
19、Kotlin 輕松入門系列
20磕潮、Kotlin 協(xié)程系列全面解讀

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末翠胰,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子自脯,更是在濱河造成了極大的恐慌之景,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件膏潮,死亡現(xiàn)場離奇詭異锻狗,居然都是意外死亡,警方通過查閱死者的電腦和手機焕参,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門轻纪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人叠纷,你說我怎么就攤上這事桐磁。” “怎么了讲岁?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵我擂,是天一觀的道長。 經(jīng)常有香客問我缓艳,道長校摩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任阶淘,我火速辦了婚禮衙吩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘溪窒。我一直安慰自己坤塞,他們只是感情好冯勉,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著摹芙,像睡著了一般灼狰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上浮禾,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天交胚,我揣著相機與錄音,去河邊找鬼盈电。 笑死蝴簇,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的匆帚。 我是一名探鬼主播熬词,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼吸重!你這毒婦竟也來了互拾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤晤锹,失蹤者是張志新(化名)和其女友劉穎摩幔,沒想到半個月后彤委,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鞭铆,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年焦影,在試婚紗的時候發(fā)現(xiàn)自己被綠了车遂。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡斯辰,死狀恐怖舶担,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情彬呻,我是刑警寧澤衣陶,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站闸氮,受9級特大地震影響剪况,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蒲跨,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一译断、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧或悲,春花似錦孙咪、人聲如沸堪唐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽淮菠。三九已至,卻和暖如春杨蛋,著一層夾襖步出監(jiān)牢的瞬間兜材,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工逞力, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留曙寡,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓寇荧,卻偏偏與公主長得像举庶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子揩抡,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容