前言
Kotlin Flow 如此受歡迎大部分歸功于其豐富靶溜、簡潔的操作符,巧妙使用Flow操作符可以大大簡化我們的程序結(jié)構(gòu)懒震,提升可讀性與可維護性罩息。
然而,雖然好用个扰,但有些操作符不太好理解瓷炮,可惜的是網(wǎng)上大部分文章只是簡單介紹其使用,并沒有梳理各個操作符的關(guān)系以及引入的緣由递宅,本篇將通過關(guān)鍵原理與使用場景串聯(lián)大部分操作符娘香,以期達到舉一反三的效果。
通過本篇文章办龄,你將了解到:
- 操作符全家福
- 單Flow操作符的原理以及使用場景
- 單Flow操作符里的多協(xié)程原理以及使用場景
- 多Flow操作符里的多協(xié)程原理以及使用場景
- Flow操作符該怎么學(xué)烘绽?
1. 操作符全家福
紅色部分為使用了多協(xié)程的操作符
上圖僅包含常用官方提供的操作符,其它未包含進來的操作符原理也是類似的俐填,當(dāng)然我們也可以封裝自己的操作符
由圖上可知安接,將操作符分為了三類:
- 構(gòu)建操作符
- 中間操作符
- 末端操作符
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。
總結(jié)來說英融,flow調(diào)用流程簡化為:兩個操作符+兩個閉包+emit函數(shù):
- collect操作符觸發(fā)調(diào)用盏檐,執(zhí)行了flow的閉包
- 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操作符。
原理很簡單:
- 作為Flow的擴展函數(shù)
- 重寫了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)
}
}
- 依然是Flow擴展函數(shù),返回一個新的Flow對象
- 新Flow對象重寫了flow閉包冷离,該閉包里調(diào)用collect收集了原始Flow的數(shù)據(jù)
- 當(dāng)數(shù)據(jù)到來后吵冒,經(jīng)過transform處理,而我們自定義的transform閉包里將數(shù)據(jù)再次發(fā)射出去
- 最后新返回的flow的collect閉包被調(diào)用
上面只是使用了一個transform操作符西剥,若是多個transform操作符痹栖,該怎么去分析呢?其實瞭空,套路是有跡可循的揪阿。
這里涉及到了一種設(shè)計模式:裝飾者模式
每調(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é)生的班主任信息锐秦,這里涉及到兩個接口:
- 請求學(xué)生信息,使用Flow1表示
- 請求該學(xué)生的班主任信息盗忱,使用Flow2表示
- 我們需要先拿到學(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原理簡單來說:
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原理簡單來說:
可以看出锄俄,zip的特點:
短的Flow結(jié)束局劲,另一個Flow也結(jié)束
5. Flow操作符該怎么學(xué)?
以上我們由淺入深分別分析了:
- 單個Flow操作符原理與使用場景
- 單個Flow操作符切換多個協(xié)程的原理與使用場景
- 多個Flow操作符切換多個協(xié)程的原理與使用場景
以上三者是遞進關(guān)系奶赠,第1點比較簡單鱼填,第2點難度適中。
尤其是第3點比較難以理解毅戈,因為涉及到了其它的知識:Channel苹丸、ChannelFlow愤惰、多協(xié)程、線程切換等赘理。
在之前的文章中有提到過:ChannelFlow是Flow復(fù)雜操作符的基礎(chǔ)宦言,想要掌握復(fù)雜操作符的原理需要明白ChannelFlow的運行機制,有興趣可移步:當(dāng)商模,Kotlin Flow與Channel相逢
建議Flow操作符學(xué)習(xí)步驟:
- 先會使用簡單的操作符filter奠旺、map等
- 再學(xué)會使用flowOn、buffer施流、callbackFlow等操作符
- 進而使用flatMapXXX以及combine响疚、zip等操作符
- 最后可以看看其實現(xiàn)原理,達到舉一反三應(yīng)用到實際需求里
Flow操作符的閉坑指南:
- 涉及到多協(xié)程的操作符嫂沉,需要關(guān)注其執(zhí)行的線程環(huán)境
- 涉及到多協(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é)程系列全面解讀