Kotlin Flow使用

一、前言:

Flow是kotlin提供的一個工具,使用協(xié)程封裝成生產(chǎn)者-消費者模式,上流來負責(zé)生產(chǎn),下流來接收消耗。

A cold asynchronous data stream that sequentially emits values
and completes normally or with an exception。
翻譯下就是:按順序發(fā)出值并正常完成或異常完成的Cold異步數(shù)據(jù)流。

  • 熱流: 無論有沒有 Subscriber 訂閱,事件始終都會發(fā)生。當(dāng) Hot Observable 有多個訂閱者時,Hot Observable 與訂閱者們的關(guān)系是一對多的關(guān)系,可以與多個訂閱者共享信息。
  • 冷流: 只有 Subscriber 訂閱時,才開始執(zhí)行發(fā)射數(shù)據(jù)流的代碼。并且 Cold Observable 和 Subscriber 只能是一對一的關(guān)系,當(dāng)有多個不同的訂閱者時,消息是重新完整發(fā)送的。也就是說對 Cold Observable 而言,有多個Subscriber的時候,他們各自的事件是獨立的。
image.png

二、Flow使用:

1、Flow 的簡單使用

(1)flow{ ... }內(nèi)部可以調(diào)用suspend 函數(shù);
(2)使用 emit() 方法來發(fā)射數(shù)據(jù);
(3)使用 collect() 方法來收集結(jié)果。

1、Flow 的簡單使用

//上流函數(shù)
fun simpleFlow() = flow {
    for (i in 1..3) {
        delay(100)
        emit(i)
    }
}
 
fun main() {
    runBlocking {
        //下流接收數(shù)據(jù)
        simpleFlow().collect { value ->
            println(value)
        }
 
        println("finished")
    }
}

結(jié)果:
1
2
3
finished

2、Flow是冷流,所以collect是掛起函數(shù),不是子協(xié)程,并且只有執(zhí)行collect函數(shù)時,上流的代碼才會被執(zhí)行,所以在一個協(xié)程中多次調(diào)用collect,它們會按順序執(zhí)行。

fun simpleFlow() = flow {
    for (i in 1..3) {
        delay(100)
        emit(i)
    }
}
 
fun main() {
    runBlocking {
        simpleFlow().collect { value ->
            println(value)
        }
 
        println("collect1 finished")
 
        simpleFlow().collect { value ->
            println(value)
        }
 
        println("collect2 finished")
    }
}

結(jié)果:
1
2
3
collect1 finished
1
2
3
collect2 finished

3、Flow的連續(xù)性

Flow也支持函數(shù)式編程,并且從上流到下流的每個過渡操作符都會處理發(fā)射值,最終流入下流

fun main() {
    runBlocking {
        flow {
            for (i in 1..5) {
                delay(100)
                emit(i)
            }
        }.filter {
            it % 2 == 0 //只取偶數(shù)
        }.map {
            "String $it"
        }.collect {
            println(it)
        }
    }
}
結(jié)果:
String 2
String 4

2、創(chuàng)建 Flow 的常用方式

(1)flow{ ... }需要顯示調(diào)用emit() 發(fā)射數(shù)據(jù);
(2)flowOf()一個發(fā)射固定流,不需要顯示調(diào)用emit() 發(fā)射數(shù)據(jù);
(3)asFlow()擴展函數(shù),可以將各種集合與序列轉(zhuǎn)換為流,也不需要顯示調(diào)用emit() 發(fā)射數(shù)據(jù);

1、flow{}

flow {
    (5 .. 10).forEach {
              emit(it)
         }
}.collect{
   println(it)
}

2、flowOf() 幫助可變數(shù)組生成 Flow 實例

flowOf(1,2,3,4,5).collect { println(it) }

其實flowOf調(diào)用的就是第一種flow{},分別emit發(fā)送值,源碼如下:

public fun <T> flowOf(vararg elements: T): Flow<T> = flow {
    for (element in elements) {
        emit(element)
    }
}

3、asFlow() 面向數(shù)組、列表等集合

 (1 ..5).asFlow().onEach {
        delay(1000)
  }.collect {
         println(it)
  }

4、注意:消費數(shù)據(jù)
collect 方法和 RxJava 中的 subscribe 方法一樣,都是用來消費數(shù)據(jù)的。
除了簡單的用法外,這里有兩個問題得注意一下:

  • collect 函數(shù)是一個 suspend 方法,所以它必須發(fā)生在協(xié)程或者帶有 suspend 的方法里面,這也是我為什么在一開始的時候啟動了
  • lifecycleScope.launch。lifecycleScope 是我使用的 Lifecycle 的協(xié)程擴展庫當(dāng)中的,你可以替換成自定義的協(xié)程作用域

三、Flow切換線程:

1、切換線程使用的是flowOn操作符。

    flow {
        for (i in 1..5) {
            delay(100)
            emit(i)
        }
    }.map {
        it * it
    }.flowOn(Dispatchers.IO)
        .collect {
            println(it)
        }

簡單點理解:就是flowOn之前的操作符運行在flowOn指定的線程之內(nèi),flowOn之后的操作符運行在整個flow運行的CoroutineContext內(nèi)。

例如,下面的代碼collect則是在main線程:

fun main() = runBlocking {
  flowOf(1,2,3,4,5)
                    .flowOn(Dispatchers.Default)
                    .collect { 
                    println(Thread.currentThread().name+" "+it) 
                }
}

打印如下:

main 1
main 2
main 3
main 4
main 5

2、除了使用子協(xié)程執(zhí)行上流外,我們還可以使用launchIn函數(shù)來讓Flow使用全新的協(xié)程上下文。

public fun <T> Flow<T>.launchIn(scope: CoroutineScope): Job = scope.launch {
    collect() // tail-call
}
fun main() {
    runBlocking {
        flow {
            println("flow :${Thread.currentThread().name}")
            for (i in 1..5) {
                delay(100)
                emit(i)
            }
        }.flowOn(Dispatchers.Default)
            .onEach { println("collect:${Thread.currentThread().name} $it") }
            .launchIn(CoroutineScope(Dispatchers.IO))
            .join()//主線程等待這個協(xié)程執(zhí)行結(jié)束
    }
}
結(jié)果:
flow :DefaultDispatcher-worker-1
collect:DefaultDispatcher-worker-1 1
collect:DefaultDispatcher-worker-1 2
collect:DefaultDispatcher-worker-1 3
collect:DefaultDispatcher-worker-1 4
collect:DefaultDispatcher-worker-1 5

四、Flow操作符

1、transform

在使用transform操作符時,可以任意多次調(diào)用emit。

runBlocking {

                (1..5).asFlow()
                    .transform {
                        emit(it * 2)
                        delay(100)
                        emit(it * 4)
                    }
                    .collect { println("transform:$it") }
            }

打印如下:

transform:2
transform:4
transform:4
transform:8
transform:6
transform:12
transform:8
transform:16
transform:10
transform:20

transform、transformLatest、transformWhile ,transform直接進行轉(zhuǎn)換,和map不同的是transform可以控制流速,transformLatest則進行最新值的轉(zhuǎn)換,類似于mapLatest ,transformWhile則要求閉包返回一個boolean值,為true則繼續(xù)返回,為false則后續(xù)的值全部取消。

日志如下:

2022-07-29 15:37:03.243 10589-10615/edu.test.demo D/Test-TAG: transform is 0
2022-07-29 15:37:04.255 10589-10615/edu.test.demo D/Test-TAG: transform is 10
2022-07-29 15:37:05.269 10589-10615/edu.test.demo D/Test-TAG: transform is 20
2022-07-29 15:37:06.281 10589-10615/edu.test.demo D/Test-TAG: transform is 30
2022-07-29 15:37:07.294 10589-10615/edu.test.demo D/Test-TAG: transform is 40
2022-07-29 15:37:08.306 10589-10615/edu.test.demo D/Test-TAG: transform is 50
2022-07-29 15:37:09.318 10589-10615/edu.test.demo D/Test-TAG: transform is 60
2022-07-29 15:37:10.330 10589-10615/edu.test.demo D/Test-TAG: transform is 70
2022-07-29 15:37:11.341 10589-10615/edu.test.demo D/Test-TAG: transform is 80
2022-07-29 15:37:12.353 10589-10615/edu.test.demo D/Test-TAG: transform is 90
2022-07-29 15:37:13.470 10589-10617/edu.test.demo D/Test-TAG: transformLatest 9
2022-07-29 15:37:13.483 10589-10617/edu.test.demo D/Test-TAG: transformWhile 0
2022-07-29 15:37:13.495 10589-10617/edu.test.demo D/Test-TAG: transformWhile 1
2022-07-29 15:37:13.509 10589-10617/edu.test.demo D/Test-TAG: transformWhile 2
2022-07-29 15:37:13.521 10589-10617/edu.test.demo D/Test-TAG: transformWhile 3
2022-07-29 15:37:13.532 10589-10617/edu.test.demo D/Test-TAG: transformWhile 4
2022-07-29 15:37:13.544 10589-10617/edu.test.demo D/Test-TAG: transformWhile 5

2、take

take操作符只取前幾個emit發(fā)射。

  (1 .. 5).asFlow().take(2).collect {
                    println("take:$it")
                }

打印結(jié)果:

take:1
take:2

taketakeWhiledropdropWhile,take則是取幾個值返回,takeWhile按條件取值,如果滿足條件就返回,不滿足則后面全部取消。drop和take相反,dropWhile和takeWhile相反。

            val flow = flow {
                repeat(10){
                    delay(10)
                    emit(it)
                }
            }
              flow.take(5).collect {
                Log.d(TAG.TAG,"take $it")
            }

            flow.takeWhile {
                it < 5
            }.collect {
                Log.d(TAG.TAG,"takeWhile $it")
            }

            flow.drop(5).collect {
                Log.d(TAG.TAG,"drop $it")
            }
            flow.dropWhile {
                it < 5
            }.collect {
                Log.d(TAG.TAG,"dropWhile $it")
            }

打印如下:

D/Test-TAG: take 0
D/Test-TAG: take 1
D/Test-TAG: take 2
D/Test-TAG: take 3
D/Test-TAG: take 4
D/Test-TAG: takeWhile 0
D/Test-TAG: takeWhile 1
D/Test-TAG: takeWhile 2
D/Test-TAG: takeWhile 3
D/Test-TAG: takeWhile 4
D/Test-TAG: drop 5
D/Test-TAG: drop 6
D/Test-TAG: drop 7
D/Test-TAG: drop 8
D/Test-TAG: drop 9
D/Test-TAG: dropWhile 5
D/Test-TAG: dropWhile 6
D/Test-TAG: dropWhile 7
D/Test-TAG: dropWhile 8
D/Test-TAG: dropWhile 9

3、reduce

runBlocking {
                val sum=( 1 ..5).asFlow()
//                    .map {
//                    //println("map:${it}")
//                    it*it  }   //1,4,9,16,25

                    .reduce { a, b ->
                        println("reduce:${a},${b}")
                        a*b
                    }

                 println(sum)

            }

打印如下:

reduce:1,2
reduce:2,3
reduce:6,4
reduce:24,5
120

簡單點理解就是兩個元素操作之后拿到的值跟后面的元素進行操作,用于把flow 簡化合并為一個值。

4、zip

fun main() = runBlocking {

    val flowA = (1..5).asFlow()
    val flowB = flowOf("one", "two", "three", "four", "five").onEach { delay(100) }

    val time = measureTimeMillis {
        flowA.zip(flowB) { a, b -> "$a and $b" }
            .collect { println(it) }
    }

    println("Cost $time ms")
}

打印如下:

1 and one
2 and two
3 and three
4 and four
5 and five
Cost 540 ms

如果flowA中的item個數(shù)大于flowB中的item個數(shù),執(zhí)行合并后新flow的item個數(shù)=較小的flow的item個數(shù)。

5、filter

filterfilterNotfilterIsInstancefilterNotNullfliter閉包返回一個Boolean值,為true則返回,false則不返回,filterNot剛好相反;filterIsInstance則進行類型過濾,如過濾出String或者Int等,filterNotNull則過濾null值,返回非空值。

            val flow = flow {
                repeat(10){
                    delay(10)
                    emit(it)
                }
            }
            flow.filter {
                it % 2 == 0
            }.collect {
                Log.d(TAG.TAG,"filter $it")
            }

            flow.filterNot {
                it % 2 == 0
            }.collect {
                Log.d(TAG.TAG,"filterNot $it")
            }

            flow {
                emit(1)
                emit("123")
            }.filterIsInstance<String>().collect {
                Log.d(TAG.TAG,"filterIsInstance $it")
            }

            flow {
                emit(1)
                emit(null)
                emit(2)
            }.filterNotNull().collect {
                Log.d(TAG.TAG,"filterNotNull $it")
            }

打印如下:

2022-07-29 15:50:45.376 10675-10703/edu.test.demo D/Test-TAG: filter 0
2022-07-29 15:50:45.400 10675-10703/edu.test.demo D/Test-TAG: filter 2
2022-07-29 15:50:45.422 10675-10703/edu.test.demo D/Test-TAG: filter 4
2022-07-29 15:50:45.444 10675-10703/edu.test.demo D/Test-TAG: filter 6
2022-07-29 15:50:45.466 10675-10703/edu.test.demo D/Test-TAG: filter 8
2022-07-29 15:50:45.505 10675-10703/edu.test.demo D/Test-TAG: filterNot 1
2022-07-29 15:50:45.528 10675-10703/edu.test.demo D/Test-TAG: filterNot 3
2022-07-29 15:50:45.550 10675-10703/edu.test.demo D/Test-TAG: filterNot 5
2022-07-29 15:50:45.574 10675-10703/edu.test.demo D/Test-TAG: filterNot 7
2022-07-29 15:50:45.597 10675-10703/edu.test.demo D/Test-TAG: filterNot 9
2022-07-29 15:50:45.598 10675-10703/edu.test.demo D/Test-TAG: filterIsInstance 123
2022-07-29 15:50:45.600 10675-10703/edu.test.demo D/Test-TAG: filterNotNull 1
2022-07-29 15:50:45.600 10675-10703/edu.test.demo D/Test-TAG: filterNotNull 2

6、merge

是將兩個flow合并起來,將每個值依次發(fā)出來

            val flow1  = listOf(1,2).asFlow()
            val flow2 = listOf("one","two","three").asFlow()
            merge(flow1,flow2).collect {value->
                Log.d(TAG.TAG,value.toString())
            }

可以看出merge在將flow1和flow2合并之后將五個值依次發(fā)送出來。

五、Flow的異常處理

當(dāng)運算符中的發(fā)射器或代碼拋出異常,可以有兩種方式處理
1.try catch
2.catch函數(shù)

1、try catch適用于收集時發(fā)生的異常

fun main() {
    runBlocking {
        val flow = flow {
            for (i in 1..3) {
                emit(i)
            }
        }
 
        try {
            flow.collect {
                println(it)
                throw RuntimeException()
            }
        } catch (e: Exception) {
            print("caught: $e")
        }
    }
}

2、雖然上流也可以使用try catch,但是更推薦catch函數(shù)

fun main() {
    runBlocking {
        val flow = flow {
            for (i in 1..3) {
                emit(i)
                throw RuntimeException()
            }
        }.catch { e ->
            print("caught1: $e")
        }.collect {
            println(it)
        }
    }
}

六、Flow的完成

  runBlocking {
            val flow = flow {
                for (i in 1..3) {
                    emit(i)
                }
            }.onStart {
                //1、開始操作符
                println("start")
            }.onCompletion {
                //1、完成操作符
                println("done")
            }.collect {
                println(it)
            }
        }

七、取消Flow

Flow也是可以被取消的,最常用的方式就是通過withTimeoutOrNull來取消,代碼如下所示。

MainScope().launch {
    withTimeoutOrNull(2500) {
        flow {
            for (i in 1..5) {
                delay(1000)
                emit(i)
            }
        }.collect {
            Log.d("xys", "Flow: $it")
        }
    }
}

這樣當(dāng)輸出1、2之后,F(xiàn)low就被取消了。

Flow的取消,實際上就是依賴于協(xié)程的取消。

八、Flow的同步非阻塞模型

首先,我們要理解下,什么叫同步非阻塞,默認場景下,F(xiàn)low在沒有切換線程的時候,運行在協(xié)程作用域指定的線程,這就是同步,那么非阻塞又是什么呢?我們知道emit和collect都是suspend函數(shù),所謂suspend函數(shù),就是會掛起,將CPU資源讓出去,這就是非阻塞,因為suspend了就可以讓一讓,讓給誰呢?讓給其它需要執(zhí)行的函數(shù),執(zhí)行完畢后,再把資源還給我。

flow {
    for (i in 0..3) {
        emit(i)
    }
}.onStart {
    Log.d("xys", "Start Flow in ${Thread.currentThread().name}")
}.onEach {
    Log.d("xys", "emit value---$it")
}.collect {
    Log.d("xys", "Result---$it")
}

輸出為:

D/xys: Start Flow in main
D/xys: emit value---0
D/xys: Result---0
D/xys: emit value---1
D/xys: Result---1
D/xys: emit value---2
D/xys: Result---2
D/xys: emit value---3
D/xys: Result---3

可以發(fā)現(xiàn),emit一個,collect拿一個,這就是同步非阻塞,互相謙讓,這樣誰都可以執(zhí)行,看上去flow中的代碼和collect中的代碼,就是同步執(zhí)行的。

九、異步非阻塞模型

假如我們給Flow增加一個線程切換,讓Flow執(zhí)行在子線程,同樣是上面的代碼,我們再來看下執(zhí)行情況

flow {
    for (i in 0..3) {
        emit(i)
    }
}.onStart {
    Log.d("xys", "Start Flow in ${Thread.currentThread().name}")
}.onEach {
    Log.d("xys", "emit value---$it")
}.flowOn(Dispatchers.IO).collect {
    Log.d("xys", "Collect Flow in ${Thread.currentThread().name}")
    Log.d("xys", "Result---$it")
}

輸出為:

D/xys: Start Flow in DefaultDispatcher-worker-1
D/xys: emit value---0
D/xys: emit value---1
D/xys: emit value---2
D/xys: emit value---3
D/xys: Collect Flow in main
D/xys: Result---0
D/xys: Collect Flow in main
D/xys: Result---1
D/xys: Collect Flow in main
D/xys: Result---2
D/xys: Collect Flow in main
D/xys: Result---3

D/xys: Result---3
這個時候,F(xiàn)low就變成了異步非阻塞模型,異步呢,就更好理解了,因為在不同線程,而此時的非阻塞,就沒什么意義了,由于flow代碼先執(zhí)行,而這里的代碼由于沒有delay,所以是同步執(zhí)行的,執(zhí)行的同時,collect在主線程進行監(jiān)聽。

除了使用flowOn來切換線程,使用channelFlow也可以實現(xiàn)異步非阻塞模型。


參考:Kotlin Flow 介紹 - 簡書 (jianshu.com)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,362評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,013評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,346評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,421評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,146評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,534評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,585評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,767評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,318評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,074評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,258評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,828評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,486評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,916評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,156評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,993評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,234評論 2 375

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