-
sequence
sequence又被稱爲惰性集合操作,下面舉例說明
fun main() { val sequence = sequenceOf(1, 2, 3, 4) val result: Sequence<Int> = sequence.map { i -> println("Map $i") i * 2 }.filter { i -> println("Filter $i") i % 3 == 0 } println(result.first())// }
執行結果如下
Map 1 Filter 2 Map 2 Filter 4 Map 3 Filter 6 6
惰性的概念首先說是在
println(result.first())
之前,關於result的map和filter都不會執行,只有result被使用的時候纔會執行,而且執行是惰性的,即map取出一個元素交給filter,而不是map對所有元素都處理過戶再交給filter,而且當滿足條件後就不會往下執行,由結果可以看出,沒有對Sequence的4進行map和filter操作,因爲3已經滿足了條件而List是沒有惰性的
fun main() { val sequence = listOf<Int>(1, 2, 3, 4) val result: List<Int> = sequence.map { i -> println("Map $i") i * 2 }.filter { i -> println("Filter $i") i % 3 == 0 } println(result.first()) }
執行結果
Map 1 Map 2 Map 3 Map 4 Filter 2 Filter 4 Filter 6 Filter 8 6
List是聲明後立即執行,處理流程如下
- {1,2,3,4}->{2,4,6,8}
- 遍歷判斷是否能被3整除
由此可以總結出Sequence的優勢
- 一旦滿足遍歷需要退出的條件,就可以省略後面不必要的遍歷
- 像List這種實現Iterable接口的集合類,每調用一次函數就會生成一個新的Iterable,下一個函數再基於新的Iterable執行,每次函數調用產生的臨時Iterable會導致額外的內存消耗,而Sequence在整個流程只有一個,且不改變原sequence
- 因此,Sequence這種數據類型可以在數據量較大或數據量未知的時候,作爲流式處理的解決方案
但是由上也可以看出Squence是同步完成這些操作的,那有沒有辦法使用異步完成那些map和filter等操作符呢,答案就是Flow
-
Flow
如果我們把list轉成Flow並在最後調用collect{},會產生一個編譯錯誤,這是因爲Flow是基於協程構建的,默認具有異步功能,因此你只能在協程裏使用它,Flow也是
cold stream
,也就是直到你調用terminal operator
(比如collect{}),Flow纔會執行,而且如果重複調用collect,則調用會得到同樣的結果Flow提供了很多操作符,比如map,filter,scan,groupBy等,它們都是
cold stream
的,我們可以利用這些操作符完成異步代碼。假如我們想使用map操作符來執行一些耗時任務(這裏我們用延遲來模擬耗時),在Rxjava中你可以使用flatmap來進行一些邏輯,比如fun main() { val disposable = Observable.fromArray("A","Ba","ora","pear","fruit") .flatMap {string->//flatMap是異步,map是同步 println("flatMap $string") Observable.just(string) .delay(1000,TimeUnit.MILLISECONDS) .map { it.length }//這裏是異步,對"A","Ba","ora","pear","fruit"異步執行轉換操作,因此下面也不是順序打印 } .subscribe{ print("subscribe:::") println(it) } Thread.sleep(10012) }
執行結果
flatMap A flatMap Ba flatMap ora flatMap pear flatMap fruit subscribe:::1 subscribe:::5 subscribe:::2 subscribe:::3 subscribe:::4
使用Flow完成類似上面的操作是這樣的
fun main() { runBlocking { flowOf("A","Ba","ora","pear","fruit") .map { stringToLength(it) } .collect { println(it) }//會依次打印 12345 } } private suspend fun stringToLength(it:String):Int{ delay(1000) return it.length }
-
terminal operator
上面提到collect()是terminal operator,意思就是僅當你調用它的時候纔會去得到結果,和sequence使用的時候纔會執行,Rxjava調用
subscribe
後纔會執行,Flow中的terminal operator是suspend函數,其他的terminal operator有toList,toSet;first(),reduce(),flod()等 -
取消 Cancellation
每次設置Rxjava訂閱時,我們都必須考慮合適取消這些訂閱,以免發生內存溢出或者,在生命週期結束後依然在後臺執行任務(expired task working in background),調用subscribe後,Rxjava會s給我們返回一個Disposable對象,在需要時利用它的disposable方法可以取消訂閱,如果你有多個訂閱,你可以把這些訂閱放在
CompositeDisposable
,並在需要的時候調用它的clear()方法或者dispose方法val compositeDisposable = CompositeDisposable() compositeDisposable.add(disposable) compositeDisposable.clear() compositeDisposable.dispose()
但是在協程作用內你完全不用考慮這些,因爲只會在作用域內執行,作用域外會自動取消
-
Errors 處理異常
Rxjava最有用的功能之一是處理錯誤的方式,你可以在onError裏處理所有的異常。同樣Flow有類似的方法
catch{}
,如果你不使用此方法,那你的應用會拋出異常或者崩潰,你可以像之前一樣使用try catch或者catch{}來處理錯誤,下面讓我們來模擬一些錯誤fun main() { runBlocking { flowOfAnimeCharacters() .map { stringToLength(it)} .collect { println(it) } } } private fun flowOfAnimeCharacters() = flow { emit("Madara") emit("Kakashi") //throwing some error throw IllegalStateException() emit("Jiraya") emit("Itachi") emit("Naruto") } private suspend fun stringToLength(it:String):Int{ delay(1000) return it.length }
如果你運行了這個代碼,那麼程序顯然會拋出異常並在控制檯打印,下面我們分別使用上述的兩種方式處理異常
fun main() { //使用try catch runBlocking { try { flowOfAnimeCharacters() .map { stringToLength(it)} .collect { println(it) } }catch (e:Exception){ println(e.stackTrace)//雖然有異常,但是我們對異常做了處理,不會導致應用崩潰 }finally { println("Beat it") } } //------------------------------------ @ExperimentalCoroutinesApi fun main() { //using catch{} runBlocking { flowOfAnimeCharacters() .map { stringToLength(it)} .catch { println(it) }//不過這個好像還是實驗性質的api,不在方法上使用註解會警告,並且catch{}必須凡在terminal operator之前 .collect { println(it) } } }
-
resume 恢復
如果代碼在執行過程中出現了異常,我們希望使用默認數據或者完整的備份來恢復數據流,在Rxjava中我們可以是使用 onErrorResumeNext()或者 onErrorReturn(),在Flow中我們依然可以使用catch{},但是我們需要在catch{}代碼塊裏使用emit()來一個一個的發送備份數據,甚至如果我們願意,可以使用emitAll()可以產生一個新的Flow,
@ExperimentalCoroutinesApi fun main() { //using catch{} runBlocking { flowOfAnimeCharacters() .catch { emitAll(flowOf("Minato", "Hashirama")) } .collect { println(it) } }
現在你可以得到如下結果
Madara Kakashi Minato Hashirama
-
flowOn()
默認情況下Flow數據會運行在調用者的上下文(線程)中,如果你想隨時切換線程比如像Rxjava的observeOn(),你可以使用flowOn()來改變上游的上下文,這裏的上游是指調用flowOn之前的所有操作符,官方文檔有很好的說明
/** * Changes the context where this flow is executed to the given [context].--改變Flow執行的上下文 * This operator is composable and affects only preceding operators that do not have its own context.---這個操作符是可以多次使用的,它僅影響操作符之前沒有自己上下文的操作符 * This operator is context preserving: [context] **does not** leak into the downstream flow.--這個操作符指定的上下文不會污染到下游,它會保留默認的上下文,例如下面例子中最後的操作符single()使用的是默認的上下文而不是上游指定的Dispatchers.Default * * For example: * * ``` * withContext(Dispatchers.Main) { * val singleValue = intFlow // will be executed on IO if context wasn't specified before * .map { ... } // Will be executed in IO * .flowOn(Dispatchers.IO) * .filter { ... } // Will be executed in Default * .flowOn(Dispatchers.Default) * .single() // Will be executed in the Main * } * ```
-
Completion
當Flow完成發送是數據時,無論是成功或者失敗,你都想做一些事情,onCompletion()可以幫助你做到這一點:如下
@ExperimentalCoroutinesApi fun main() { //using catch{} runBlocking { flowOfAnimeCharacters() .flowOn(Dispatchers.Default) .catch { emitAll(flowOf("Minato", "Hashirama")) } .onCompletion { println("Done") it?.let { throwable -> println(throwable) }//這裏由於上面處理了異常,所以這裏就不會再有異常傳遞,這裏自然也不會執行 } .collect { println(it) } } } //catch{}的作用就是捕獲異常並恢復新的Flow,這使得我們最終得到原始數據“Madara”, “Kakashi”和備份數據 “Minato”, “Hashirama”,捕獲異常之後就是一份全新的沒有異常的數據, onCompletion{…} and catch{…}都是mediator operators,他們時使用的順序很重要
-
總結
我們使用Flow構建器創建了Flow,其中最基本的構建器是flowOf(),創建之後運行這個Flow需要使用terminal operators,由於terminal operator是suspend function ,因此我們需要在協程作用域內編寫Flow代碼,如果你不想使用這種嵌套調用而是鏈式調用,你可以使用 onEach{…}集合launchIn()。使用catch{}操作符來處理異常,並且當發生異常時也可以提供一個備份數據(如果你想這麼做)。當上遊的數據處理完或發生異常之後,使用onCompletion()來執行一些操作(感覺有點像finally)。。所有的操作符都會默認運行在調用函數的上下文中,可以使用flowOn()來切換上游的上下文
Kotlin之Flow由淺入深,對比Rxjava
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.