在 Kotlin 中,Flow 防抖(Debounce)主要用于处理连续事件流,防止在短时间内频繁触发操作。这在用户输入、搜索建议、按钮点击等场景中非常有用。
一、防抖的基本概念
防抖的核心思想:在事件触发后,等待一段时间,如果在这段时间内没有新的事件触发,才执行操作;如果有新事件,则重新计时。
二、Kotlin Flow 中的防抖操作符
1. debounce() 操作符
kotlin
fun <T> Flow<T>.debounce(timeoutMillis: Long): Flow<T>
2. 基本使用示例
kotlin
// 模拟用户输入
fun simulateUserInput(): Flow<String> = flow {
emit("A")
delay(100)
emit("AB")
delay(200)
emit("ABC")
delay(300)
emit("ABCD")
}
fun main() = runBlocking {
simulateUserInput()
.debounce(250) // 250ms 防抖时间
.collect { value ->
println("Debounced: $value at ${System.currentTimeMillis()}")
}
}
三、实际应用场景
1. 搜索框防抖
kotlin
class SearchViewModel {
private val _searchQuery = MutableStateFlow("")
val searchResults: Flow<List<String>>
init {
searchResults = _searchQuery
.debounce(300) // 300ms 防抖
.filter { it.length >= 2 } // 至少2个字符才搜索
.distinctUntilChanged() // 值变化时才触发
.flatMapLatest { query ->
performSearch(query)
}
}
fun onQueryChanged(query: String) {
_searchQuery.value = query
}
private fun performSearch(query: String): Flow<List<String>> = flow {
// 模拟网络请求
delay(500)
emit(listOf("result1", "result2"))
}
}
2. 按钮点击防抖
kotlin
class ButtonDebounceExample {
private val _buttonClicks = MutableSharedFlow<Unit>()
val debouncedClicks = _buttonClicks
.debounce(1000) // 1秒内只允许一次点击
.onEach {
performAction()
}
suspend fun onClick() {
_buttonClicks.emit(Unit)
}
private fun performAction() {
println("Button clicked at ${System.currentTimeMillis()}")
}
}
四、防抖与其他操作符的结合使用
1. 结合 filter() 和 distinctUntilChanged()
kotlin
val searchFlow = userInputFlow
.filter { it.isNotBlank() }
.debounce(300)
.distinctUntilChanged() // 避免连续相同的值
.flatMapLatest { query ->
searchApi.search(query)
}
2. 结合 onEach() 进行调试
kotlin
flowOf("A", "B", "C")
.onEach { println("原始值: $it") }
.debounce(100)
.onEach { println("防抖后: $it") }
.collect()
五、防抖与节流(Throttle)的区别
| 特性 | 防抖 (Debounce) | 节流 (Throttle) |
|---|---|---|
| 目的 | 等待稳定后执行 | 固定频率执行 |
| 适用场景 | 搜索输入、窗口调整 | 滚动事件、游戏循环 |
| Flow 操作符 | debounce() |
sample() 或自定义 |
kotlin
// 防抖示例:只在用户停止输入后执行
inputFlow.debounce(300)
// 节流示例:每300ms最多执行一次
inputFlow.throttleLatest(300)
六、自定义防抖逻辑
如果需要更复杂的防抖逻辑,可以自定义操作符:
kotlin
fun <T> Flow<T>.customDebounce(
timeout: Long,
coroutineScope: CoroutineScope
): Flow<T> = channelFlow {
var job: Job? = null
collect { value ->
job?.cancel() // 取消前一个任务
job = coroutineScope.launch {
delay(timeout)
send(value)
}
}
}
七、注意事项
-
超时时间选择
kotlin// 太短:可能过于频繁 .debounce(50) // 太长:用户体验差 .debounce(2000) // 合适:通常 200-500ms .debounce(300) -
冷流 vs 热流
StateFlow和SharedFlow是热流,适合防抖- 普通冷流每次收集都会重新开始
-
资源管理
kotlin.debounce(300) .onCompletion { println("Flow completed") } .catch { e -> println("Error: $e") }
八、完整示例
kotlin
class DebounceExample {
private val searchQuery = MutableStateFlow("")
val searchResults = searchQuery
.debounce(300)
.filter { it.length >= 3 }
.distinctUntilChanged()
.flatMapLatest { query ->
fetchSearchResults(query)
.catch { e ->
emit(emptyList())
println("Search error: $e")
}
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = emptyList()
)
fun updateQuery(query: String) {
searchQuery.value = query
}
private fun fetchSearchResults(query: String): Flow<List<String>> = flow {
// 模拟网络请求
delay(500)
val results = listOf("$query result1", "$query result2")
emit(results)
}
}
总结
Kotlin Flow 的 debounce() 操作符是实现防抖的简洁有效方式。合理使用防抖可以:
- 减少不必要的网络请求
- 提升应用性能
- 改善用户体验
- 降低服务器压力
根据具体场景选择合适的防抖时间,并结合其他操作符如 filter()、distinctUntilChanged()、flatMapLatest() 等,可以构建出高效的事件处理流水线。