前言
Flow是配合Kotlin協程使用的異步編程工具。其實Flow的概念並不是獨家的,更早之前Java端就有自帶的stream與大名鼎鼎的RxJava,它們的思想都是響應式編程思想(或者也可以稱呼鏈式編程),當時的響應式編程思想就是爲了解決Java各個線程的異步處理結果進行同步。其更底層的思想核心是觀察者模式或者生產消費者模式。所以回到Flow,它看似複雜有很多新概念,其實核心歸根結底就是觀察者模式思想與生產消費者模式思想。
Flow的使用結構
一個簡單的例子
一個極簡的例子快速瞭解一下,emit爲發射,collect爲接收
fun demo1() {
GlobalScope.launch {
flow<Int> {
for (i in 0..10) {
//emit爲發射函數
emit(i)
}
}.collect {
//collect接收,它還是掛起函數必須在協程中調用
Log.e("zh", "結果 = ${it} ")
}
}
}
/* 輸出結果:
結果 = 0
結果 = 1
結果 = 2
結果 = 3
結果 = 4
結果 = 5
結果 = 6
結果 = 7
結果 = 8
結果 = 9
結果 = 10
*/
構建方式
Flow有多種多樣的構建方式,下面一一舉例:
方式一 默認方式
以默認的方式創建flow,上面的例子已經舉例了,這裏再舉例一下。
fun demo1() {
GlobalScope.launch {
flow<String> {
delay(500)
emit("蘋果")
delay(500)
emit("西瓜")
delay(500)
emit("香蕉")
delay(500)
emit("哈密瓜")
}.collect{
Log.e("zh", "時間 = ${System.currentTimeMillis()} 結果 = ${it}", )
}
}
}
/*
輸出結果:
時間 = 1690598408614 結果 = 蘋果
時間 = 1690598409116 結果 = 西瓜
時間 = 1690598409617 結果 = 香蕉
時間 = 1690598410118 結果 = 哈密瓜
*/
方式二 從集合發射流
通過集合調用asFlow()函數
fun demo2() {
GlobalScope.launch {
//從集合發射流
(0..3).asFlow().collect {
Log.e("zh", "結果 = ${it}", )
}
listOf<String>("蘋果","西瓜","香蕉","哈密瓜").asFlow().collect{
Log.e("zh", "結果 = ${it}", )
}
}
}
/*
輸出結果:
結果 = 0
結果 = 1
結果 = 2
結果 = 3
結果 = 蘋果
結果 = 西瓜
結果 = 香蕉
結果 = 哈密瓜
*/
方式三 自建集合發射流
通過調用flowOf()函數
fun demo2() {
GlobalScope.launch {
flowOf("蘋果" to 1, "香蕉" to 2, "西瓜" to 3).collect { pair ->
Log.e("zh", "結果 = ${pair.first} ${pair.second}", )
}
}
}
/*
輸出結果:
結果 = 蘋果 1
結果 = 香蕉 2
結果 = 西瓜 3
*/
冷流
Flow是冷流的,冷流的概念是隻有訂閱者訂閱後,發佈者纔會生產數據。 並且訂閱者與發佈者是一一對應關係,數據只會給目標訂閱者發送,不會發送給其他。這類似於你去工廠下訂單,工廠纔會生產你需要的產品。
有冷流就會有熱流的概念,熱流其實是觀察者模式(或者廣播)的概念,發佈端無論有沒有訂閱者,都會始終執行,並且有多個訂閱者時,發佈端跟訂閱者是一對多的關係,熱流可以與多個訂閱者共享信息。
以下代碼驗證Flow的冷流特性:
fun demo3() {
val flow = flow<String> {
val simpleDateFormat = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
val timeString = simpleDateFormat.format(System.currentTimeMillis())
emit(timeString)
}
GlobalScope.launch {
for (index in 0..2){
delay(1000)
flow.collect {
//collect調用它了,flow纔會執行
Log.e("zh", "結果 = ${it} ")
}
}
}
}
/*
輸出結果:
結果 = 13:53:53
結果 = 13:53:54
結果 = 13:53:55
*/
背壓概念 與 collectLatest 停止當前工作以收集最新值
Backpressure(背壓)
Backpressure(背壓)是指在異步場景中,被觀察者發送事件速度遠快於觀察者的處理速度的情況。在上面的例子中收集都使用了collect函數, collect會收集每一個值並且保證執行完處理代碼。 但是如果collect正在處理耗時數據,而flow的發送端是不會停下來,發送端會一直髮送,而collect則會堵塞發過來的數據,等待當前collect函數代碼塊裏的耗時邏輯處理完,纔會執行下一個發送過來的數據。這樣會導致我們看到最後最新的數據時已經延遲了很久。
collectLatest 停止當前工作以收集最新值
上面的背壓情況,在一些業務下並不希望耗時等待,只想儘快看到最後最新的數據,這個時候就可以使用collectLatest來替代collect進行數據收集。collectLatest並不會保證每一個發送來的時間都處理完,當有新數據發送過來時,如果collectLatest的代碼塊沒有執行完成,它會立刻停止處理老數據。去處理最新發送過來的數據。
代碼例子
fun demo1() {
GlobalScope.launch {
flow<Int> {
for (i in 0..2){
//請注意這裏延遲50毫秒是爲了對比下面collectLatest的延遲
delay(50)
emit(i)
}
}.collectLatest{
//這裏分別延遲了2次50毫秒,是爲了表示這個代碼塊裏的耗時,是始終大於上面發送端的。
delay(50)
Log.e("zh", "模擬正在耗時處理 = ${it}")
delay(50)
Log.e("zh", "最終處理完結果 = ${it}")
}
}
}
/*
輸出結果:
模擬正在耗時處理 = 0
模擬正在耗時處理 = 1
模擬正在耗時處理 = 2
最終處理完結果 = 2
*/
filter-過濾
filter屬於中間操作符,將數據過濾
fun demo3() {
GlobalScope.launch {
flow<Int> {
for (i in 0..10){
emit(i)
}
}.filter {
//除餘,過濾掉奇數
it % 2 == 0
}.collect{
Log.e("zh", "結果 = ${it}")
}
}
}
/*
輸出結果:
結果 = 0
結果 = 2
結果 = 4
結果 = 6
結果 = 8
結果 = 10
*/
filterNot-過濾
filterNot屬於中間操作符, 它是反向過濾的,會將true的結果去除,將false結果繼續發射
fun demo3() {
GlobalScope.launch {
flow<Int> {
for (i in 0..10){
emit(i)
}
}.filterNot {
//除餘,過濾掉偶數
it % 2 == 0
}.collect{
Log.e("zh", "結果 = ${it}")
}
}
}
/*
輸出結果:
結果 = 1
結果 = 3
結果 = 5
結果 = 7
結果 = 9
*/
filterNotNull-過濾null值
filterNotNull屬於中間操作符,它會將null過濾掉。
fun demo1() {
GlobalScope.launch {
flow<String?> {
//這裏的集合故意添加了一個null值
listOf<String?>("蘋果", "香蕉", null, "芒果").forEach {
Log.e("zh", "要發送的值 = ${it}")
emit(it)
}
}.filterNotNull().collect {
Log.e("zh", "結果 = ${it}")
}
}
}
/*
輸出結果:
要發送的值 = 蘋果
結果 = 蘋果
要發送的值 = 香蕉
結果 = 香蕉
要發送的值 = null
要發送的值 = 芒果
結果 = 芒果
*/
filterIsInstance-類型過濾
filterIsInstance屬於中間操作符,它會過濾數據類型
fun demo1() {
GlobalScope.launch {
flow {
emit("蘋果")
emit(1)
emit(true)
emit("芒果")
}.filterIsInstance<String>() //這裏的filterIsInstance類型爲String,過濾除了String以外的類型
.collect {
Log.e("zh", "結果 = ${it}")
}
}
}
/*
輸出結果:
結果 = 蘋果
結果 = 芒果
*/
map-轉換組合
map屬於中間操作符,用於將數據轉換或者組合
fun demo3() {
val fruits = listOf<String>("蘋果", "香蕉", "芒果")
GlobalScope.launch {
flow<Int> {
for (i in 0..2) {
emit(i)
}
}.map { it: Int ->
Pair<String, Int>(fruits[it], it)
}.collect { it: Pair<String, Int> -> //這裏其實可以不用寫it: Pair<String, Int>,寫出來就是想表達,上面的flow<Int>被轉換成了Pair<String, Int>
Log.e("zh", "結果 = ${it}")
}
}
}
/*
輸出結果:
結果 = (蘋果, 0)
結果 = (香蕉, 1)
結果 = (芒果, 2)
*/
mapLatest-轉換組合
mapLatest屬於中間操作符
有時候我們的map方法處理轉換需要耗時,但是發送端會一直髮射新數據,導致map的耗時堆積積累,這會出現獲取最後一個數據時已經耗時了很久。在一些情況下我們並不關切前面的數據,只希望獲取最新的數據,這裏就可以使用mapLatest。
fun demo3() {
val timeFormat = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
val flow = flow<Int> {
for (i in 1..3){
emit(i)
}
}
GlobalScope.launch {
//這裏實現map作爲下面mapLatest的對比
flow.map {
//這裏寫30毫秒延遲,模擬處理數據需要耗時
delay(30)
timeFormat.format(System.currentTimeMillis()) to it
}.collect{
Log.e("zh", "map >> 結果 = ${it}")
}
flow.mapLatest {
//這裏寫30毫秒延遲,模擬處理數據需要耗時
delay(30)
timeFormat.format(System.currentTimeMillis()) to it
}.collect{
Log.e("zh", "mapLatest >> 最新數據結果 = ${it}")
}
}
}
/*
輸出結果:
map >> 結果 = (15:25:03, 1)
map >> 結果 = (15:25:03, 2)
map >> 結果 = (15:25:03, 3)
mapLatest >> 最新數據結果 = (15:25:03, 3)
*/
mapNotNull-轉換組合不包含null值
mapNotNull屬於中間操作符
fun demo1() {
val fruits = listOf<String>("蘋果","芒果","水蜜桃","茄子")
GlobalScope.launch {
flow<String> {
for (item in fruits){
emit(item)
}
}.mapNotNull {
if (it == "茄子"){
//茄子不屬於水果,返回null
null
} else {
System.currentTimeMillis() to it
}
}.collect{
Log.e("zh", "結果 = ${it}")
}
}
}
/*
輸出結果:
結果 = (1690620115923, 蘋果)
結果 = (1690620115923, 芒果)
結果 = (1690620115923, 水蜜桃)
*/
drop-棄用指定數量前段數據
drop屬於中間操作符,它會棄用你給定數量的前段數據。
fun demo1() {
GlobalScope.launch {
flow {
emit(1)
emit(2)
emit(3)
emit(4)
}.drop(2).collect{
Log.e("zh", "結果 = ${it}")
}
}
}
/*
輸出結果:
結果 = 3
結果 = 4
*/
dropWhile-找到首個不滿足條件的值,然後繼續發送它和剩下的數據
dropWhile屬於中間操作符,你不能簡單的將它理解成判斷範圍(這變成跟上面的過濾操作符一樣了),它其實用來找到數據集合的執行出發點。
fun demo1() {
GlobalScope.launch {
flow {
emit(1)
emit(2)
emit(3)
emit(4)
emit(5)
emit(6)
}.dropWhile { it != 3 } //因爲dropWhile的特性,3這個值也會繼續發送
.collect {
Log.e("zh", "結果 = ${it}")
}
}
}
/*
輸出結果:
結果 = 3
結果 = 4
結果 = 5
結果 = 6
*/
take-只允許指定數量的前段數據發送
take屬於中間操作符,它只允許指定數量的前段數據發送
fun demo1() {
GlobalScope.launch {
flow {
emit(1)
emit(2)
emit(3)
emit(4)
emit(5)
emit(6)
}.take(3).collect {
Log.e("zh", "結果 = ${it}")
}
}
}
/*
輸出結果:
結果 = 1
結果 = 2
結果 = 3
*/
takeWhile-找到首個不滿足條件的值,發送它前面的值
takeWhile屬於中間操作符
fun demo1() {
GlobalScope.launch {
flow {
emit(1)
emit(2)
emit(3)
emit(4)
emit(5)
emit(6)
}.takeWhile { it != 3 }.collect {
Log.e("zh", "結果 = ${it}")
}
}
}
/*
輸出結果:
結果 = 1
結果 = 2
*/
debounce-防抖動,指定接受數據的超時時間
debounce屬於中間操作符,請注意,debounce這個函數有點複雜。它的意思是指定超時時間,如果發送端邏輯耗時大於超時時間就不發送(但是如果一直都超時就會發送最後一個數據),只有發送端邏輯耗時小於超時時間才發送。
定時接收
切換線程
異常捕獲
end