忙裏偷閒,寫個小文章,一直拖更了一年的【思貨】協程網絡請求的最終章等過年再寫吧android
用戶搜索時,爲了不產生無心義的搜索請求,一般會進行搜索數據限流。熟悉RxJava
的同窗,必定會知道怎麼作,各類天花亂墜的操做符讓你眼花繚亂。markdown
那麼用上了kotlin
的小夥伴,徹底能夠沒必要使用RxJava
,由於kotlin
中自帶的Flow
就能夠作到。不廢話,直接開始。網絡
激起我寫做的緣由是這一篇文章Kotlin Flow 開發應用實踐之搜索優化,這篇文章錯誤的使用了Flow,目前做者已經對文章內部的錯誤進行了更改,可是我仍是想借機來講明一下。隨後給出正確的解釋及正確的代碼。ide
我先把那篇文章中錯誤的代碼貼上來:函數
// 錯誤代碼🙅
binding.etSearch.doOnTextChanged { text, _, _, _ ->
searchFilter(text.toString())
}
private fun searchFilter(str:String){
flow { emit(str) }
.debounce(400)
.filter {
it.isNotEmpty()
}
.catch { LogUtils.d(it.message) }
.flowOn(Dispatchers.Default)
.onEach {
LogUtils.d("輸出:$it")
binding.tvShow.text = it.toString()
}.flowOn(Dispatchers.Main)
.launchIn(lifecycleScope)
}
複製代碼
仔細看上面的代碼,大家本身想一想錯在了哪裏?oop
文本輸入框etSearch
每次文本的變化都會回掉searchFilter()
方法,而方法裏面每次都去實例化了一個flow
,而後又使用了debounce()
限流,那麼這個debounce()
限流的意義何在?毫無心義啊,對吧。由於每次都是新建立的flow
啊。post
還不懂的話,那就看下面簡化的代碼:優化
// 文章錯誤代碼🙅的簡化
for (i in 0..100) {
// 模擬生成數據
flow<Int> {
emit(i)
}.debounce(500) // 這裏是無效的 限流,由於 flow 的 emit 只執行了一次啊……
.collect {
println("----------------->>> $it")
}
}
複製代碼
那麼咱們理想中的正確代碼邏輯是這樣的:ui
flow<Int> {
for (i in 0..100) {
// 模擬生成數據
emit(i)
}
}.debounce(500) // 這裏是有效的 限流
.collect {
println("----------------->>> $it")
}
複製代碼
好了,同窗們能夠再對比以上兩段代碼,for
循環就是咱們模擬的輸入數據,這個for
循環在裏面和在外面是兩個徹底不同的邏輯~再也不多作解釋了spa
對於輸入框的這類業務邏輯,單純使用flow
是沒法到達目的的,由於寫不出來。有的小朋友要站起來高喊了,「怎麼寫不出來,你瞎說,我來寫」,一頓操做寫出了下面的代碼:
// 小朋友寫的錯誤代碼🙅
flow<Editable> {
editText.doAfterTextChanged { text ->
emit(text) // 這裏是錯誤的,emit不能夠寫在內部類中
}
}.debounce(500)
.collect {
println("----------------->>> $it")
}
複製代碼
錯誤的地方我寫上了註釋,emit
是一個suspend
掛起函數,是不能夠寫在內部類裏的,代碼直接編譯不經過。
首先我提一個知識點,你們回憶一下。RxJava
中的流,是分爲冷流和熱流(即:cold Observable和 hot Observable)對吧。若是你說啥,流還分冷熱?親,那這裏建議你炒個回鍋肉呢。
在RxJava
的使用中,不注意區分冷熱流,是致使RxJava
錯用、濫用的緣由之一!
這裏我只用兩句話簡單解釋冷熱流,不展開講RxJava
。
冷流:只有觀察者進行訂閱了,上游纔開始執行發射數據
熱流:不管有沒有觀察者,上游的數據都會發射
直接用註釋說明:
flow {
// 發射數據
}.collect {
/* 只有執行了 collect 或者 collectLast 訂閱了流, 上游 flow 裏面的代碼塊纔會執行! */
}
複製代碼
如今這位小朋友,冷靜一下,想一想輸入框的業務場景是什麼樣子的?即:無論有沒有訂閱者,只要EditText
文本變化了,都會發送數據。那咱們就應該用熱流來解決問題。
各位大佬直接看代碼吧:
// 定義一個全局的 StateFlow
private val _etState = MutableStateFlow("")
override fun onCreate(savedInstanceState: Bundle?) {
et.doAfterTextChanged { text ->
// 往流裏寫數據
_etState.value = (text ?: "").toString()
}
lifecycleScope.launch {
_etState
.sample(500) // 限流,500毫秒
.filter {
// 空文本過濾掉
it.isNotBlank()
}.collect {
// 訂閱數據
println("----------------->>> $it")
}
}
}
複製代碼
好了,收工了。對於 StateFlow
,Google是這麼解釋的:「與使用 flow
構建器構建的冷數據流不一樣,StateFlow
是熱數據流:從數據流收集數據不會觸發任何提供方代碼。StateFlow
始終處於活躍狀態並存於內存中,並且只有在垃圾回收根中未涉及對它的其餘引用時,它才符合垃圾回收條件。」
最後的代碼中,我是用了sample()
方法來做爲限流,而沒有使用debounce()
方法限流。那麼這兩種限流的方法有什麼區別呢?有興趣的同窗本身在代碼裏嘗試一下吧,會得出本身的結論的。
(對於此場景,我更傾向於sample()
方法限流)。