Kotlin 學(xué)習(xí)筆記艱難地來(lái)到了第五篇~ 在這一篇主要會(huì)說(shuō) Flow 的基本知識(shí)和實(shí)例焕蹄。由于 Flow 內(nèi)容較多,所以會(huì)分幾個(gè)小節(jié)來(lái)講解蕾久,這是第一小節(jié),文章后面會(huì)結(jié)合一個(gè)實(shí)例介紹 Flow 在實(shí)際開(kāi)發(fā)中的應(yīng)用新娜。
首先回想一下,在協(xié)程中處理某個(gè)操作既绩,我們只能返回單個(gè)結(jié)果概龄;而 Flow 可以按順序返回多個(gè)結(jié)果,在官方文檔中饲握,F(xiàn)low 被翻譯為 數(shù)據(jù)流
私杜,這也說(shuō)明了 Flow 適用于多值返回的場(chǎng)景。
Flow 是以協(xié)程為基礎(chǔ)構(gòu)建的互拾,所以它可通過(guò)異步的方式處理一組數(shù)據(jù)歪今,所要處理的數(shù)據(jù)類(lèi)型必須相同,比如: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ù)刀崖,通過(guò)流上的一些中間操作得出結(jié)果;若出錯(cuò)可拋出異常拍摇。這些 “流上的中間操作” 包括但不限于 map
亮钦、filter
、take
充活、zip
等等方法蜂莉。這些中間操作是鏈?zhǔn)降睦ⅲ梢栽诤竺嬖俅翁砑悠渌僮鞣椒ǎ⑶乙膊皇菕炱鸷瘮?shù)映穗,它們只是構(gòu)建了一條鏈?zhǔn)降牟僮鞑?shí)時(shí)返回結(jié)果給后面的操作步驟窖张。
流上的終端操作符要么是掛起函數(shù),例如 collect
男公、single
荤堪、toList
等等合陵,要么是在給定作用域內(nèi)開(kāi)始收集流的 launchIn
操作符枢赔。前半句好理解,后半句啥意思拥知?這就得看一下 launchIn
這個(gè)終端操作符的作用了踏拜。它里面是這樣的:
//code 1
public fun <T> Flow<T>.launchIn(scope: CoroutineScope): Job = scope.launch {
collect() // tail-call
}
原來(lái) launchIn
方法可以傳入一個(gè) CoroutineScope
協(xié)程作用域,然后在這個(gè)作用域里面調(diào)用 collect
方法低剔。lifecycleScope
速梗、MainScope()
這些都是協(xié)程作用域,所以 launchIn
方法只不過(guò)是 scope.launch { flow.collect() }
的一種簡(jiǎn)寫(xiě)襟齿。
流的執(zhí)行也被稱(chēng)之為收集流姻锁,并且是以掛起的方式,不是阻塞的猜欺。流最終的執(zhí)行成功與否取決于流上的操作是否全部執(zhí)行成功位隶。collect
函數(shù)就是最常見(jiàn)的收集流函數(shù)。
1.1 冷流與熱流
冷流(Cold Flow):在數(shù)據(jù)被使用方訂閱后开皿,即調(diào)用 collect
方法之后涧黄,提供方才開(kāi)始執(zhí)行發(fā)送數(shù)據(jù)流的代碼,通常是調(diào)用 emit
方法赋荆。即不消費(fèi)笋妥,不生產(chǎn),多次消費(fèi)才會(huì)多次生產(chǎn)窄潭。使用方和提供方是一對(duì)一的關(guān)系春宣。
熱流(Hot Flow):無(wú)論有無(wú)使用方,提供方都可以執(zhí)行發(fā)送數(shù)據(jù)流的操作嫉你,提供方和使用方是一對(duì)多的關(guān)系月帝。熱流就是不管有無(wú)消費(fèi),都可生產(chǎn)均抽。
SharedFlow
就是熱流的一種嫁赏,任何流也可以通過(guò) stateIn
和 shareIn
操作轉(zhuǎn)化為熱流,或者通過(guò) produceIn
操作將流轉(zhuǎn)化為一個(gè)熱通道也能達(dá)到目的油挥。本篇只介紹冷流相關(guān)知識(shí)潦蝇,熱流會(huì)在后面小節(jié)講解~
2. Flow 構(gòu)建方法
Flow 的構(gòu)造方法有如下幾種:
1款熬、 flowOf()
方法。用于快速創(chuàng)建流攘乒,類(lèi)似于 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ù)類(lèi)型的數(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è)方法分別類(lèi)似于 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
來(lái)調(diào)用 emit
方法去發(fā)射值藤违。因此,在 flow{...}
中不要通過(guò)創(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
來(lái)調(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、MutableStateFlow
和 MutableSharedFlow
方法:都可以定義相應(yīng)的構(gòu)造函數(shù)去創(chuàng)建一個(gè)可以直接更新的熱流雄卷。由于篇幅有限搓蚪,有關(guān)熱流的知識(shí)后面小節(jié)會(huì)再說(shuō)明。
3. Flow 常用的操作符
Flow 的使用依賴(lài)于眾多的操作符丁鹉,這些操作符可以大致地分為 中間操作符 與 末端操作符 兩大類(lèi)妒潭。中間操作符是流上的中間操作悴能,可以針對(duì)流上的數(shù)據(jù)做一些修改,是鏈?zhǔn)秸{(diào)用雳灾。中間操作符與末端操作符的區(qū)別是:中間操作符是用來(lái)執(zhí)行一些操作漠酿,不會(huì)立即執(zhí)行,返回值還是個(gè) Flow谎亩;末端操作符就會(huì)觸發(fā)流的執(zhí)行炒嘲,返回值不是 Flow。
一個(gè)完整的 Flow 是由 Flow 構(gòu)建器
匈庭、Flow 中間操作符
夫凸、Flow 末端操作符
組成,如下示意圖所示:
3.1 collect 末端操作符
最常見(jiàn)的當(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í)行開(kāi)始時(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("++++ 開(kāi)始")
emit(40)
println("++++ 發(fā)出了40")
emit(50)
println("++++ 發(fā)出了50")
}
lifecycleScope.launch {
testFlow2.collect{
println("++++ 收集 = $it")
}
}
// 輸出結(jié)果:
//com.example.myapplication I/System.out: ++++ 開(kāi)始
//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ù),也可如例子中寫(xiě)成尾調(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
方法可以通過(guò)傳入 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
順著代碼咱先來(lái)看看一些常用的 Flow 中間操作符。
1)map
:用來(lái)將 Flow 中的數(shù)據(jù)一個(gè)個(gè)拿出來(lái)做各自的處理娄涩,然后交給下一個(gè)操作符窗怒;本例中就是將 Flow 中的數(shù)據(jù)進(jìn)行平方處理;
2)check()
:類(lèi)似于一個(gè)檢查站蓄拣,滿足括號(hào)內(nèi)條件的數(shù)據(jù)可以通過(guò)扬虚,不滿足則交給它的尾調(diào)函數(shù)處理,并且拋出異常球恤;
3)onCompletion
:Flow 最后的兜底器辜昵。無(wú)論 Flow 最后是執(zhí)行完成、被取消碎捺、拋出異常路鹰,都會(huì)走到 onCompletion
操作符中,類(lèi)似于在 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
:不用多說(shuō),專(zhuān)門(mén)用于捕捉異常的,避免程序崩潰碑诉。這里如果把 catch
去掉彪腔,程序就會(huì)崩潰。如果把 catch
和 onCompletion
操作符位置調(diào)換进栽,則 onCompletion
里面就接收不到異常信息了德挣,如圖所示。
5. Flow 數(shù)據(jù)請(qǐng)求實(shí)例
說(shuō)了這么多快毛,舉個(gè)在實(shí)際中經(jīng)常用到的數(shù)據(jù)請(qǐng)求的例子吧格嗅。先來(lái)看一個(gè)最簡(jiǎn)單的例子:
5.1 單接口請(qǐng)求
現(xiàn)在一般都是在 ViewModel 里持有 LiveData 數(shù)據(jù),并且進(jìn)行數(shù)據(jù)的請(qǐng)求唠帝,所以先來(lái)看下 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
類(lèi)對(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 中的代碼也是一般常見(jiàn)的代碼:
interface ApiService {
@GET("users")
suspend fun getUsers(): List<ApiUser>
}
再回過(guò)來(lái)看看 ViewModel 的代碼瀑晒,從 apiHelperImpl.getUsers
方法后面的 catch
和 collect
操作符也可看出惰说,getUsers
方法返回的就是一個(gè) Flow 對(duì)象赚瘦,其使用的構(gòu)造方法就是前文中說(shuō)到的 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)求邑退,該如何處理呢竹宋?這就需要用到之前說(shuō)的 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ù)的所在線程妹懒,類(lèi)似于 RxJava 中的 subscribeOn
方法雀监。例如 flowOn(Dispatchers.Default)
就是將 Flow 的操作都放到后臺(tái)線程中執(zhí)行。
當(dāng) flowOn
操作符之前沒(méi)有設(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é)是相同的版扩,而且兩者都有豐富的操作符來(lái)處理各種不同的情況。不同的是 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ì)開(kāi)始執(zhí)行析桥;冷流的使用方和提供方是一對(duì)一的司草;
4)簡(jiǎn)單介紹了 collect
、reduce
末端操作符以及 zip
泡仗、map
等中間操作符的使用埋虹;
5)Flow 異常處理所用到的 catch
、check
娩怎、onCompletion
等操作符的用法搔课;
6)Flow 在數(shù)據(jù)請(qǐng)求上的實(shí)例
所用實(shí)例來(lái)源: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)
- Android 上的 Kotlin 數(shù)據(jù)流崩瓤;官方文檔 https://developer.android.com/kotlin/flow
- Flow Kotlin 官方文檔袍啡; https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/
- 【Kotlin Flow】 一眼看全——Flow操作符大全; 搬磚小子出現(xiàn)了 https://juejin.cn/post/6989536876096913439
- 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
- Understanding Terminal Operators in Kotlin Flow; Amit Shekhar; https://blog.mindorks.com/terminal-operators-in-kotlin-flow
- Creating Flow Using Flow Builder in Kotlin; Amit Shekhar; https://blog.mindorks.com/creating-flow-using-flow-builder-in-kotlin
- Exception Handling in Kotlin Flow; Amit Shekhar; https://blog.mindorks.com/exception-handling-in-kotlin-flow