Kotlin 學(xué)習(xí)筆記(五)—— Flow 數(shù)據(jù)流學(xué)習(xí)實(shí)踐指北(一)

Kotlin 學(xué)習(xí)筆記艱難地來到了第五篇~ 在這一篇主要會(huì)說 Flow 的基本知識(shí)和實(shí)例。由于 Flow 內(nèi)容較多,所以會(huì)分幾個(gè)小節(jié)來講解,這是第一小節(jié),文章后面會(huì)結(jié)合一個(gè)實(shí)例介紹 Flow 在實(shí)際開發(fā)中的應(yīng)用。

首先回想一下,在協(xié)程中處理某個(gè)操作,我們只能返回單個(gè)結(jié)果;而 Flow 可以按順序返回多個(gè)結(jié)果,在官方文檔中,F(xiàn)low 被翻譯為 數(shù)據(jù)流,這也說明了 Flow 適用于多值返回的場(chǎng)景。

Flow 是以協(xié)程為基礎(chǔ)構(gòu)建的,所以它可通過異步的方式處理一組數(shù)據(jù),所要處理的數(shù)據(jù)類型必須相同,比如:Flow<Int>是處理整型數(shù)據(jù)的數(shù)據(jù)流。

Flow 一般包含三個(gè)部分:
1)提供方:負(fù)責(zé)生成數(shù)據(jù)并添加到 Flow 中,得益于協(xié)程,F(xiàn)low 可以異步生成數(shù)據(jù);
2)中介(可選):可對(duì) Flow 中的值進(jìn)行操作、修改;也可修改 Flow 本身的一些屬性,如所在線程等;
3)使用方:接收并使用 Flow 中的值。
提供方:生產(chǎn)者,使用方:消費(fèi)者,典型的生產(chǎn)者消費(fèi)者模式。

1. Flow 概述

Flow 是一個(gè)異步數(shù)據(jù)流,它可以順序地發(fā)出數(shù)據(jù),通過流上的一些中間操作得出結(jié)果;若出錯(cuò)可拋出異常。這些 “流上的中間操作” 包括但不限于 map、filter、takezip 等等方法。這些中間操作是鏈?zhǔn)降?,可以在后面再次添加其他操作方法,并且也不是掛起函?shù),它們只是構(gòu)建了一條鏈?zhǔn)降牟僮鞑?shí)時(shí)返回結(jié)果給后面的操作步驟。

流上的終端操作符要么是掛起函數(shù),例如 collect、singletoList 等等,要么是在給定作用域內(nèi)開始收集流的 launchIn 操作符。前半句好理解,后半句啥意思?這就得看一下 launchIn 這個(gè)終端操作符的作用了。它里面是這樣的:

//code 1
public fun <T> Flow<T>.launchIn(scope: CoroutineScope): Job = scope.launch {
    collect() // tail-call
}

原來 launchIn 方法可以傳入一個(gè) CoroutineScope 協(xié)程作用域,然后在這個(gè)作用域里面調(diào)用 collect 方法。lifecycleScope、MainScope() 這些都是協(xié)程作用域,所以 launchIn 方法只不過是 scope.launch { flow.collect() } 的一種簡(jiǎn)寫。

流的執(zhí)行也被稱之為收集流,并且是以掛起的方式,不是阻塞的。流最終的執(zhí)行成功與否取決于流上的操作是否全部執(zhí)行成功。collect 函數(shù)就是最常見的收集流函數(shù)。

1.1 冷流與熱流

冷流(Cold Flow):在數(shù)據(jù)被使用方訂閱后,即調(diào)用 collect 方法之后,提供方才開始執(zhí)行發(fā)送數(shù)據(jù)流的代碼,通常是調(diào)用 emit 方法。即不消費(fèi),不生產(chǎn),多次消費(fèi)才會(huì)多次生產(chǎn)。使用方和提供方是一對(duì)一的關(guān)系。

熱流(Hot Flow):無論有無使用方,提供方都可以執(zhí)行發(fā)送數(shù)據(jù)流的操作,提供方和使用方是一對(duì)多的關(guān)系。熱流就是不管有無消費(fèi),都可生產(chǎn)。

SharedFlow 就是熱流的一種,任何流也可以通過 stateInshareIn 操作轉(zhuǎn)化為熱流,或者通過 produceIn 操作將流轉(zhuǎn)化為一個(gè)熱通道也能達(dá)到目的。本篇只介紹冷流相關(guān)知識(shí),熱流會(huì)在后面小節(jié)講解~

2. Flow 構(gòu)建方法

Flow 的構(gòu)造方法有如下幾種:
1、 flowOf() 方法。用于快速創(chuàng)建流,類似于 listOf() 方法,下面是它的源碼:

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

所以用法也比較簡(jiǎn)單:

//code 3
val testFlow = flowOf(65,66,67)
lifecycleScope.launch {
    testFlow.collect {
        println("輸出:$it")
    }
}
//打印結(jié)果:
//輸出:65
//輸出:66
//輸出:67

注意到 Flow 初始化的時(shí)候跟其他對(duì)象一樣,作用域在哪兒都可以,但 collect 收集的時(shí)候就需要放在協(xié)程里了,因?yàn)?collect 是個(gè)掛起函數(shù)。

2、asFlow() 方法。是集合的擴(kuò)展方法,可將其他數(shù)據(jù)轉(zhuǎn)換成 Flow,例如 Array 的擴(kuò)展方法:

//code 4
public fun <T> Array<T>.asFlow(): Flow<T> = flow {
    forEach { value ->
        emit(value)
    }
}

不僅 Array 擴(kuò)展了此方法,各種其他數(shù)據(jù)類型的數(shù)組都擴(kuò)展了此方法。所以集合可以很方便地構(gòu)造一個(gè) Flow。

3、flow {···} 方法。這個(gè)方法可以在其內(nèi)部順序調(diào)用 emit 方法或 emitAll 方法從而構(gòu)造一個(gè)順序執(zhí)行的 Flow。emit 是發(fā)射單個(gè)值;emitAll 是發(fā)射一個(gè)流,這兩個(gè)方法分別類似于 list.add(item)list.addAll(list2) 方法。flow {···} 方法的源碼如下:

//code 5
public fun <T> flow(@BuilderInference block: suspend FlowCollector<T>.() -> Unit): Flow<T> = SafeFlow(block)

需要額外注意的是,flow 后面的 lambda 表達(dá)式是一個(gè)掛起函數(shù),里面不能使用不同的 CoroutineContext 來調(diào)用 emit 方法去發(fā)射值。因此,在 flow{...} 中不要通過創(chuàng)建新協(xié)程或使用 withContext 代碼塊在另外的 CoroutineContext 中調(diào)用 emit 方法,否則會(huì)報(bào)錯(cuò)。如果確實(shí)有這種需求,可以使用 channelFlow 操作符。

//code 6
val testFlow = flow {
    emit(23)
//    withContext(Dispatchers.Main) { // error
//        emit(24)
//    }
    delay(3000)
    emitAll(flowOf(25,26))
}

4、channelFlow {···} 方法。這個(gè)方法就可以在內(nèi)部使用不同的 CoroutineContext 來調(diào)用 send 方法去發(fā)射值,而且這種構(gòu)造方法保證了線程安全也保證了上下文的一致性,源碼如下:

//code 7
public fun <T> channelFlow(@BuilderInference block: suspend ProducerScope<T>.() -> Unit): Flow<T> =
    ChannelFlowBuilder(block)

一個(gè)簡(jiǎn)單的使用例子:

//code 8
val testFlow1 = channelFlow {
    send(20)
    withContext(Dispatchers.IO) { //可切換線程
        send(22)
    }
}
lifecycleScope.launch {
    testFlow1.collect {
        println("輸出 = $it")
    }
}

5、MutableStateFlowMutableSharedFlow 方法:都可以定義相應(yīng)的構(gòu)造函數(shù)去創(chuàng)建一個(gè)可以直接更新的熱流。由于篇幅有限,有關(guān)熱流的知識(shí)后面小節(jié)會(huì)再說明。

3. Flow 常用的操作符

Flow 的使用依賴于眾多的操作符,這些操作符可以大致地分為 中間操作符末端操作符 兩大類。中間操作符是流上的中間操作,可以針對(duì)流上的數(shù)據(jù)做一些修改,是鏈?zhǔn)秸{(diào)用。中間操作符與末端操作符的區(qū)別是:中間操作符是用來執(zhí)行一些操作,不會(huì)立即執(zhí)行,返回值還是個(gè) Flow;末端操作符就會(huì)觸發(fā)流的執(zhí)行,返回值不是 Flow。

一個(gè)完整的 Flow 是由 Flow 構(gòu)建器、Flow 中間操作符Flow 末端操作符 組成,如下示意圖所示:

圖1 Flow 的組成

3.1 collect 末端操作符

最常見的當(dāng)然是 collect 操作符。它是個(gè)掛起函數(shù),需要在協(xié)程作用域中調(diào)用;并且它是一個(gè)末端操作符,末端操作符就是實(shí)際啟動(dòng) Flow 執(zhí)行的操作符,這一點(diǎn)跟 RxJava 中的 Observable 對(duì)象的執(zhí)行很像。

熟悉 RxJava 的同學(xué)知道,在 RxJava 中,Observable 對(duì)象的執(zhí)行開始時(shí)機(jī)是在被一個(gè)訂閱者(subscriber) 訂閱(subscribe) 的時(shí)候,即在 subscribe 方法調(diào)用之前,Observable 對(duì)象的主體是不會(huì)執(zhí)行的。

Flow 也是相同的工作原理,F(xiàn)low 在調(diào)用 collect 操作符收集流之前,F(xiàn)low 構(gòu)建器和中間操作符都不會(huì)執(zhí)行。舉個(gè)栗子:

//code 9
val testFlow2 = flow {
    println("++++ 開始")
    emit(40)
    println("++++ 發(fā)出了40")
    emit(50)
    println("++++ 發(fā)出了50")
}
lifecycleScope.launch {
    testFlow2.collect{
        println("++++ 收集 = $it")
    }
}

// 輸出結(jié)果:
//com.example.myapplication I/System.out: ++++ 開始
//com.example.myapplication I/System.out: ++++ 收集 = 40
//com.example.myapplication I/System.out: ++++ 發(fā)出了40
//com.example.myapplication I/System.out: ++++ 收集 = 50
//com.example.myapplication I/System.out: ++++ 發(fā)出了50

從輸出結(jié)果可以看出,每次到 collect 方法調(diào)用時(shí),才會(huì)去執(zhí)行 emit 方法,而在此之前,emit 方法是不會(huì)被調(diào)用的。這種 Flow 就是冷流。

3.2 reduce 末端操作符

reduce 也是一個(gè)末端操作符,它的作用就是將 Flow 中的數(shù)據(jù)兩兩組合接連進(jìn)行處理,跟 Kotlin 集合中的 reduce 操作符作用相同。舉個(gè)栗子:

//code 10
private fun reduceOperator() {
    val testFlow = listOf("w","i","f","i").asFlow()
    CoroutineScope(Dispatchers.Default).launch {
        val result = testFlow.reduce { accumulator, value ->
            println("+++accumulator = $accumulator  value = $value")
            "$accumulator$value"
        }
        println("+++final result = $result")
    }
}

//輸出結(jié)果:
//com.example.myapplication I/System.out: +++accumulator = w  value = i
//com.example.myapplication I/System.out: +++accumulator = wi  value = f
//com.example.myapplication I/System.out: +++accumulator = wif  value = i
//com.example.myapplication I/System.out: +++final result = wifi

看結(jié)果就知道,reduce 操作符的處理邏輯了,兩個(gè)值處理后得到的新值作為下一輪中的輸入值之一,這就是兩兩接連進(jìn)行處理的意思。

圖1 中出現(xiàn)的 toList 操作符也是一種末端操作符,可以將 Flow 返回的多個(gè)值放進(jìn)一個(gè) List 中返回,返回的 List 也可以自己設(shè)置,比較簡(jiǎn)單,感興趣的同學(xué)可自行動(dòng)手試驗(yàn)。

3.3 zip 中間操作符

zip 顧名思義,就是可以將兩個(gè) Flow 匯合成一個(gè) Flow,舉個(gè)栗子就知道了:

//code 11
lateinit var testFlow1: Flow<String>
lateinit var testFlow2: Flow<String>
private fun setupTwoFlow() {
    testFlow1 = flowOf("Red", "Blue", "Green")
    testFlow2 = flowOf("fish", "sky", "tree", "ball")
    CoroutineScope(Dispatchers.IO).launch {
        testFlow1.zip(testFlow2) { firstWord, secondWord ->
            "$firstWord $secondWord"
        }.collect {
            println("+++ $it +++")
        }
    }
}

// 輸出結(jié)果:
//com.example.myapplication I/System.out: +++ Red fish +++
//com.example.myapplication I/System.out: +++ Blue sky +++
//com.example.myapplication I/System.out: +++ Green tree +++

//zip 方法聲明:
public fun <T1, T2, R> Flow<T1>.zip(other: Flow<T2>, transform: suspend (T1, T2) -> R): Flow<R> = zipImpl(this, other, transform)

zip 方法的聲明中可知,zip 方法的第二個(gè)參數(shù)就是針對(duì)兩個(gè) Flow 進(jìn)行各種處理的掛起函數(shù),也可如例子中寫成尾調(diào)函數(shù)的樣子,返回值是處理之后的 Flow。而且當(dāng)兩個(gè) Flow 長(zhǎng)度不一樣時(shí),最后的結(jié)果會(huì)默認(rèn)剔除掉先前較長(zhǎng)的 Flow 中的元素。所以 testFlow2 中的 “ball” 就被自動(dòng)剔除掉了。

4. Flow 異常處理

正如 RxJava 框架中的 subscribe 方法可以通過傳入 Observer 對(duì)象在其 onNext、onComplete、onError 返回之前處理的結(jié)果,F(xiàn)low 也有諸如 catch、onCompletion 等操作符去處理執(zhí)行的結(jié)果。例如下面的代碼:

//code 12
private fun handleExceptionDemo() {
    val testFlow = (1..5).asFlow()
    CoroutineScope(Dispatchers.Default).launch {
        testFlow.map {
            check(it != 3) {
                //it == 3 時(shí),會(huì)走到這里
                println("+++ catch value = $it")
            }
            println("+++ not catch value = $it")
            it * it
        }.onCompletion {
            println("+++ onCompletion value = $it")
        }.catch { exception ->
            println("+++ catch exception = $exception")
        }.collect{
            println("+++ collect value = $it")
        }
    }
}

//輸出結(jié)果:
//com.example.myapplication I/System.out: +++ not catch value = 1
//com.example.myapplication I/System.out: +++ collect value = 1
//com.example.myapplication I/System.out: +++ not catch value = 2
//com.example.myapplication I/System.out: +++ collect value = 4
//com.example.myapplication I/System.out: +++ catch value = 3
//com.example.myapplication I/System.out: +++ onCompletion value = java.lang.IllegalStateException: kotlin.Unit
//com.example.myapplication I/System.out: +++ catch exception = java.lang.IllegalStateException: kotlin.Unit

順著代碼咱先來看看一些常用的 Flow 中間操作符。
1)map :用來將 Flow 中的數(shù)據(jù)一個(gè)個(gè)拿出來做各自的處理,然后交給下一個(gè)操作符;本例中就是將 Flow 中的數(shù)據(jù)進(jìn)行平方處理;
2)check() :類似于一個(gè)檢查站,滿足括號(hào)內(nèi)條件的數(shù)據(jù)可以通過,不滿足則交給它的尾調(diào)函數(shù)處理,并且拋出異常;
3)onCompletion :Flow 最后的兜底器。無論 Flow 最后是執(zhí)行完成、被取消、拋出異常,都會(huì)走到 onCompletion 操作符中,類似于在 Flow 的 collect 函數(shù)外加了個(gè) try,finally。官方給了個(gè)小栗子,還是很清楚的:

//code 13
try {
    myFlow.collect { value ->
        println(value)
    }
} finally {
    println("Done")
}
//上述代碼可以替換為下面的代碼:
myFlow
    .onEach { println(it) }
    .onCompletion { println("Done") }
    .collect()

所以,在 code 12 中的 onCompletion 操作符可以接住從 check 那兒拋出的異常;
4)catch :不用多說,專門用于捕捉異常的,避免程序崩潰。這里如果把 catch 去掉,程序就會(huì)崩潰。如果把 catchonCompletion 操作符位置調(diào)換,則 onCompletion 里面就接收不到異常信息了,如圖所示。

圖2 catch 操作符

5. Flow 數(shù)據(jù)請(qǐng)求實(shí)例

說了這么多,舉個(gè)在實(shí)際中經(jīng)常用到的數(shù)據(jù)請(qǐng)求的例子吧。先來看一個(gè)最簡(jiǎn)單的例子:

5.1 單接口請(qǐng)求

現(xiàn)在一般都是在 ViewModel 里持有 LiveData 數(shù)據(jù),并且進(jìn)行數(shù)據(jù)的請(qǐng)求,所以先來看下 ViewModel 中的代碼實(shí)現(xiàn):

//code 14
class SingleNetworkCallViewModel: ViewModel() {
    private val users = MutableLiveData<Resource<List<ApiUser>>>()
    private val apiHelperImpl = ApiHelperImpl(RetrofitBuilder.apiService)

    fun fetchUsers() {
        viewModelScope.launch {
            users.postValue(Resource.loading(null))
            apiHelperImpl.getUsers()
                .catch { e ->
                    users.postValue(Resource.error(e.toString(), null))
                }
                .collect {
                    users.postValue(Resource.success(it))
                }
        }
    }

    fun getUsersData(): LiveData<Resource<List<ApiUser>>> {
        return users
    }
}

從代碼可看出,fetchUsers 方法就是數(shù)據(jù)請(qǐng)求方法,里面的核心方法是 ApiHelperImpl 類對(duì)象的 getUsers 方法,在之前初始化 apiHelperImpl 對(duì)象時(shí)傳入了一個(gè) RetrofitBuilder.apiService 值,所以底層還是用到了 Retrofit 框架進(jìn)行的網(wǎng)絡(luò)請(qǐng)求。Retrofit 相關(guān)的代碼如下:

//code 15
object RetrofitBuilder {
    private const val BASE_URL = "https://xxxxxxx/"

    private fun getRetrofit(): Retrofit {
        return Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    val apiService: ApiService = getRetrofit().create(ApiService::class.java)
}

//ApiService 中的代碼也是一般常見的代碼:
interface ApiService {
    @GET("users")
    suspend fun getUsers(): List<ApiUser>
}

再回過來看看 ViewModel 的代碼,從 apiHelperImpl.getUsers 方法后面的 catchcollect 操作符也可看出,getUsers 方法返回的就是一個(gè) Flow 對(duì)象,其使用的構(gòu)造方法就是前文中說到的 flow{} 方法:

//code 16
class ApiHelperImpl(private val apiService: ApiService) : ApiHelper {
    override fun getUsers(): Flow<List<ApiUser>> {
        return flow { emit(apiService.getUsers()) }
    }
}

ApiHelper 其實(shí)就是一個(gè)接口,規(guī)定了 ApiHelperImpl 中數(shù)據(jù)請(qǐng)求的方法名及返回值,返回值是一個(gè) Flow,里面是我們最終需要的數(shù)據(jù)列表:

//code 17
interface ApiHelper {
    fun getUsers(): Flow<List<ApiUser>>
}

Flow 調(diào)用 emit 發(fā)出去的就是 Retrofit 進(jìn)行數(shù)據(jù)請(qǐng)求后返回的 List<ApiUser> 數(shù)據(jù)。

如何在 Activity 中使用就是之前使用 LiveData 的常規(guī)操作了:

//code 18
private fun setupObserver() {
    viewModel.getUsersData().observe(this, Observer {
        when (it.status) {
            Status.SUCCESS -> {
                progressBar.visibility = View.GONE
                it.data?.let { users -> renderList(users) }
                recyclerView.visibility = View.VISIBLE
            }
            Status.LOADING -> {
                progressBar.visibility = View.VISIBLE
                recyclerView.visibility = View.GONE
            }
            Status.ERROR -> {
                //Handle Error
                progressBar.visibility = View.GONE
                Toast.makeText(this, it.message, Toast.LENGTH_SHORT).show()
            }
        }
    })
}

5.2 雙接口并行請(qǐng)求

上述例子是最簡(jiǎn)單的單個(gè)數(shù)據(jù)接口請(qǐng)求的場(chǎng)景,如果是兩個(gè)或是多個(gè)數(shù)據(jù)接口需要并行請(qǐng)求,該如何處理呢?這就需要用到之前說的 Flow 中的 zip 操作符了。接著上面的例子,再添加一個(gè)數(shù)據(jù)請(qǐng)求方法 getMoreUsers ,那么兩個(gè)接口并行的例子為:

//code 18
fun fetchUsers() {
    viewModelScope.launch {
        users.postValue(Resource.loading(null))
        apiHelper.getUsers()
            .zip(apiHelper.getMoreUsers()) { usersFromApi, moreUsersFromApi ->
                val allUsersFromApi = mutableListOf<ApiUser>()
                allUsersFromApi.addAll(usersFromApi)
                allUsersFromApi.addAll(moreUsersFromApi)
                return@zip allUsersFromApi
            }
            .flowOn(Dispatchers.Default)
            .catch { e ->
                users.postValue(Resource.error(e.toString(), null))
            }
            .collect {
                users.postValue(Resource.success(it))
            }
    }
}

兩個(gè)數(shù)據(jù)接口請(qǐng)求的快慢肯定不一樣,但不用擔(dān)心,zip 操作符會(huì)等待兩個(gè)接口的數(shù)據(jù)都返回之后才進(jìn)行拼接并交給后面的操作符處理,所以這里還需要調(diào)用 flowOn 操作符將線程切換到后臺(tái)線程中去掛起等待。但后面的 collect 操作符執(zhí)行的代碼是在主線程中,感興趣的同學(xué)可以打印線程信息看看,這就需要了解一下 flowOn 操作符的用法了。

flowOn 方法可以切換 Flow 處理數(shù)據(jù)的所在線程,類似于 RxJava 中的 subscribeOn 方法。例如 flowOn(Dispatchers.Default) 就是將 Flow 的操作都放到后臺(tái)線程中執(zhí)行。

當(dāng) flowOn 操作符之前沒有設(shè)置任何的協(xié)程上下文,那么 flowOn 操作符可以為它之前的操作符設(shè)置執(zhí)行所在的線程,并不會(huì)影響它之后下游的執(zhí)行所在線程。下面是一個(gè)簡(jiǎn)單例子:

//code 19
private fun flowOnDemo() {
    val testFlow = (1..2).asFlow()
    MainScope().launch {
        testFlow
            .filter {
                println("1+++ $it  ${Thread.currentThread().name}")
                it != 3
            }.flowOn(Dispatchers.IO)
            .map {
                println("2+++ $it  ${Thread.currentThread().name}")
                it*it
            }.flowOn(Dispatchers.Main)
            .filter {
                println("3+++ $it  ${Thread.currentThread().name}")
                it!=25
            }.flowOn(Dispatchers.IO)
            .collect{
                println("4+++ $it  ${Thread.currentThread().name}")
            }
    }
}

//輸出結(jié)果:
//com.example.myapplication I/System.out: 1+++ 1  DefaultDispatcher-worker-1
//com.example.myapplication I/System.out: 1+++ 2  DefaultDispatcher-worker-1
//com.example.myapplication I/System.out: 2+++ 1  main
//com.example.myapplication I/System.out: 2+++ 2  main
//com.example.myapplication I/System.out: 3+++ 1  DefaultDispatcher-worker-1
//com.example.myapplication I/System.out: 3+++ 4  DefaultDispatcher-worker-1
//com.example.myapplication I/System.out: 4+++ 1  main
//com.example.myapplication I/System.out: 4+++ 4  main

發(fā)現(xiàn)了么?flowOn 操作符只對(duì)最近的上游操作符線程負(fù)責(zé),它下游的線程會(huì)自動(dòng)切換到之前所在的線程。如果連續(xù)有兩個(gè)或多個(gè) flowOn 操作符切換線程,則會(huì)切換到首個(gè) flowOn 操作符切換的線程中去:

//code 20
testFlow
    .filter {
        println("1+++ $it  ${Thread.currentThread().name}")
        it != 3    //最終會(huì)在 Main 主線程中執(zhí)行
    }.flowOn(Dispatchers.Main).flowOn(Dispatchers.IO).flowOn(Dispatchers.Default)
    .collect{
        println("4+++ $it  ${Thread.currentThread().name}")
}

filter 后面連續(xù)有兩個(gè) flowOn 操作符,但最終會(huì)在 Main 線程中執(zhí)行 filter 操作符中的邏輯。

整體上看,F(xiàn)low 在數(shù)據(jù)請(qǐng)求時(shí)所扮演的角色是數(shù)據(jù)接收與處理后發(fā)送給 UI 層的作用,這跟 RxJava 的職責(zé)是相同的,而且兩者都有豐富的操作符來處理各種不同的情況。不同的是 Flow 是將接收到的數(shù)據(jù)放到 Flow 載體中,而 RxJava 一般將數(shù)據(jù)放到 Observable 對(duì)象中;Flow 處理數(shù)據(jù)更加方便和自然,去除了 RxJava 中繁多且功能臃腫的操作符。

總結(jié)

最后總結(jié)一下 Flow 第一小節(jié)的內(nèi)容吧:
1)Flow 數(shù)據(jù)流可異步按順序返回多個(gè)數(shù)據(jù);
2)Flow 整體是由 構(gòu)建器、中間操作符、末端操作符 組成;
3)冷流只有在調(diào)用末端操作符時(shí),流的構(gòu)造器和中間操作符才會(huì)開始執(zhí)行;冷流的使用方和提供方是一對(duì)一的;
4)簡(jiǎn)單介紹了 collect、reduce 末端操作符以及 zipmap 等中間操作符的使用;
5)Flow 異常處理所用到的 catchcheck、onCompletion 等操作符的用法;
6)Flow 在數(shù)據(jù)請(qǐng)求上的實(shí)例
所用實(shí)例來源:https://github.com/MindorksOpenSource/Kotlin-Flow-Android-Examples

更多內(nèi)容,歡迎關(guān)注公眾號(hào):修之竹

贊人玫瑰,手留余香!歡迎點(diǎn)贊、轉(zhuǎn)發(fā)~ 轉(zhuǎn)發(fā)請(qǐng)注明出處~

參考文獻(xiàn)

  1. Android 上的 Kotlin 數(shù)據(jù)流;官方文檔 https://developer.android.com/kotlin/flow
  2. Flow Kotlin 官方文檔; https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/
  3. 【Kotlin Flow】 一眼看全——Flow操作符大全; 搬磚小子出現(xiàn)了 https://juejin.cn/post/6989536876096913439
  4. What is Flow in Kotlin and how to use it in Android Project?; Himanshu Singh; https://blog.mindorks.com/what-is-flow-in-kotlin-and-how-to-use-it-in-android-project
  5. Understanding Terminal Operators in Kotlin Flow; Amit Shekhar; https://blog.mindorks.com/terminal-operators-in-kotlin-flow
  6. Creating Flow Using Flow Builder in Kotlin; Amit Shekhar; https://blog.mindorks.com/creating-flow-using-flow-builder-in-kotlin
  7. Exception Handling in Kotlin Flow; Amit Shekhar; https://blog.mindorks.com/exception-handling-in-kotlin-flow
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容