概念
防抖(Debounce) 的核心思想在事件触发后,等待一段时间,如果在这段时间内没有新的事件触发,才执行操作;如果有新事件,则重新计时。
节流(Throttle) 是控制事件频率的重要操作符,它确保在指定时间间隔内最多只处理一次事件。与防抖不同,节流是定期执行,而不是等待稳定。
去重(distinctUntilChanged) 是流处理中非常重要的操作,它确保只发射与上一个值不同的值,避免不必要的处理和更新。
一、防抖 (Debounce)
1. 核心概念
等待一段时间,期间没有新事件才执行
kotlin
fun <T> Flow<T>.debounce(timeoutMillis: Long): Flow<T>
2. 工作原理
text
输入: A----B----------C---D--------E
时间: | 300ms | | 300ms |
输出: B D E
3. 代码示例
kotlin
// 搜索框防抖
class SearchViewModel {
private val _searchQuery = MutableStateFlow("")
val searchResults = _searchQuery
.debounce(300) // 用户停止输入300ms后才搜索
.filter { it.length >= 2 }
.flatMapLatest { query ->
searchApi.search(query)
.catch { emit(emptyList()) }
}
.stateIn(viewModelScope, SharingStarted.Lazily, emptyList())
fun onQueryChanged(query: String) {
_searchQuery.value = query
}
}
// 详细示例
fun main() = runBlocking {
val flow = flow {
emit("A") // t=0
delay(100) // t=100
emit("AB") // t=100 (被取消)
delay(200) // t=300
emit("ABC") // t=300 (满足300ms条件)
delay(100) // t=400
emit("ABCD") // t=400 (被取消)
delay(400) // t=800
emit("ABCDE") // t=800 (满足300ms条件)
}
flow.debounce(300)
.collect { println("防抖输出: $it") }
// 输出: ABC, ABCDE
}
4. 适用场景
- 搜索框输入
- 窗口大小调整
- 表单验证
- 实时保存
二、节流 (Throttle)
1. 核心概念
固定时间内只执行一次
kotlin
// Kotlin 没有内置的 throttle,但可以用其他方式实现
fun <T> Flow<T>.throttleFirst(timeWindow: Long): Flow<T> = channelFlow {
var lastEmissionTime = 0L
collect { value ->
val currentTime = System.currentTimeMillis()
if (currentTime - lastEmissionTime > timeWindow) {
send(value)
lastEmissionTime = currentTime
}
}
}
// 或者使用 sample (Kotlin 1.7.0+)
// .sample(periodMillis)
2. 工作原理
text
输入: A-B---C-D---E-F---G
时间: | 300ms | 300ms | 300ms |
输出: A C E G
3. 代码示例
kotlin
// 实现 throttleFirst
fun <T> Flow<T>.throttleFirst(periodMillis: Long): Flow<T> = flow {
var lastTime = 0L
collect { value ->
val currentTime = System.currentTimeMillis()
if (currentTime - lastTime >= periodMillis) {
emit(value)
lastTime = currentTime
}
}
}
// 使用示例
fun main() = runBlocking {
val flow = flow {
repeat(10) {
emit(it)
delay(100) // 每100ms发射一个
}
}
flow.throttleFirst(300) // 每300ms最多一个
.collect { println("节流输出: $it") }
// 输出: 0, 3, 6, 9
}
// 按钮防连点
class ButtonViewModel {
private val _clicks = MutableSharedFlow<Unit>()
val throttledClicks = _clicks
.throttleFirst(1000) // 1秒内只能点击一次
.onEach { performAction() }
suspend fun onClick() = _clicks.emit(Unit)
}
4. 节流类型对比
kotlin
// throttleFirst - 时间窗口内的第一个
// throttleLast - 时间窗口内的最后一个
fun <T> Flow<T>.throttleLast(periodMillis: Long): Flow<T> = flow {
coroutineScope {
val channel = produceIn(this)
var lastValue: T? = null
var hasValue = false
launch {
while (true) {
delay(periodMillis)
if (hasValue) {
emit(lastValue!!)
hasValue = false
}
}
}
for (value in channel) {
lastValue = value
hasValue = true
}
}
}
5. 适用场景
- 按钮防连点
- 滚动事件
- 实时位置更新
- 游戏循环
三、去重 (distinctUntilChanged)
1. 核心概念
只发射与上一次不同的值
kotlin
fun <T> Flow<T>.distinctUntilChanged(): Flow<T>
fun <T, K> Flow<T>.distinctUntilChanged(selector: (T) -> K): Flow<T>
2. 工作原理
text
输入: A A B B B C A A
输出: A B C A
3. 代码示例
kotlin
// 基本使用
fun main() = runBlocking {
val flow = flow {
emit(1)
emit(1) // 被过滤
emit(2)
emit(2) // 被过滤
emit(1) // 与前一个不同,发射
emit(3)
}
flow.distinctUntilChanged()
.collect { println("去重后: $it") }
// 输出: 1, 2, 1, 3
}
// 自定义比较逻辑
data class User(val id: Int, val name: String)
fun main() = runBlocking {
val usersFlow = flow {
emit(User(1, "Alice"))
emit(User(1, "Alice")) // 相同ID,被过滤
emit(User(2, "Bob"))
emit(User(1, "Alice")) // ID不同,发射
}
// 只根据ID去重
usersFlow.distinctUntilChanged { old, new ->
old.id == new.id
}.collect { println("User: ${it.id} - ${it.name}") }
// 输出: User(1,Alice), User(2,Bob), User(1,Alice)
// 或者使用 keySelector
usersFlow.distinctUntilChanged { it.id }
.collect { println("User: ${it.id} - ${it.name}") }
}
4. 复杂对象去重
kotlin
// 处理列表变化
val listFlow = flow {
emit(listOf(1, 2, 3))
emit(listOf(1, 2, 3)) // 相同内容,被过滤
emit(listOf(1, 2))
emit(listOf(1, 2, 3, 4))
}
// 比较列表内容
listFlow.distinctUntilChanged { old, new ->
old == new
}
// 处理网络状态
sealed class NetworkState {
object Loading : NetworkState()
data class Success(val data: String) : NetworkState()
data class Error(val message: String) : NetworkState()
}
val stateFlow = MutableStateFlow<NetworkState>(NetworkState.Loading)
// 防止重复的 Loading 状态
stateFlow.distinctUntilChanged()
.collect { state ->
when (state) {
is NetworkState.Loading -> showLoading()
is NetworkState.Success -> showData(state.data)
is NetworkState.Error -> showError(state.message)
}
}
四、三者的对比与组合
1. 对比表
| 特性 | 防抖 (Debounce) | 节流 (Throttle) | 去重 (distinctUntilChanged) |
|---|---|---|---|
| 目的 | 等待稳定 | 限制频率 | 避免重复 |
| 输出时间 | 停止后 | 固定间隔 | 值变化时 |
| 适用场景 | 输入结束处理 | 连续事件 | 状态更新 |
| 内存 | 需要计时器 | 需要计时器 | 只需缓存前值 |
2. 组合使用示例
kotlin
// 搜索功能完整实现
class AdvancedSearchViewModel {
private val _query = MutableStateFlow("")
val searchResults = _query
.filter { it.isNotBlank() }
.debounce(300) // 防抖:停止输入300ms后
.distinctUntilChanged() // 去重:查询变化才搜索
.throttleFirst(1000) // 节流:1秒内最多一次
.flatMapLatest { query ->
performSearch(query)
.retry(2) // 失败重试2次
.catch { emit(SearchResult.Error(it)) }
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = SearchResult.Empty
)
private suspend fun performSearch(query: String): Flow<SearchResult> {
return flow {
emit(SearchResult.Loading)
delay(500) // 模拟网络请求
val results = repository.search(query)
emit(SearchResult.Success(results))
}
}
}
// 实时数据监控
class SensorMonitor {
private val _sensorData = MutableSharedFlow<SensorData>()
val processedData = _sensorData
.throttleFirst(100) // 节流:100ms采样一次
.filter { it.isValid() } // 过滤无效数据
.distinctUntilChanged { old, new ->
abs(old.value - new.value) < 0.01 // 变化小于0.01视为相同
}
.debounce(500) // 稳定500ms后持久化
.onEach { saveToDatabase(it) }
.shareIn(
scope = CoroutineScope(Dispatchers.Default),
started = SharingStarted.Lazily
)
}
3. 性能优化组合
kotlin
// 处理高频事件
fun <T> Flow<T>.optimizeForUI(
debounceTime: Long = 300,
throttleTime: Long = 16, // ~60fps
distinct: Boolean = true
): Flow<T> = flow {
if (distinct) {
distinctUntilChanged()
.debounce(debounceTime)
.throttleFirst(throttleTime)
.collect { emit(it) }
} else {
debounce(debounceTime)
.throttleFirst(throttleTime)
.collect { emit(it) }
}
}
// 使用
viewModel.dataFlow
.optimizeForUI(debounceTime = 200, throttleTime = 32)
.onEach { updateUI(it) }
.launchIn(lifecycleScope)
五、实际应用场景
1. 搜索功能
kotlin
searchQueryFlow
.filter { it.length >= 3 }
.debounce(300) // 停止输入300ms
.distinctUntilChanged() // 避免相同查询
.flatMapLatest { query ->
searchRepository.search(query)
}
2. 实时聊天
kotlin
messageFlow
.throttleFirst(100) // 防止消息轰炸
.distinctUntilChanged { old, new ->
old.id == new.id // 避免重复消息
}
.onEach { displayMessage(it) }
3. 表单验证
kotlin
formInputFlow
.debounce(500) // 输入完成500ms后验证
.distinctUntilChanged() // 值变化才验证
.map { validate(it) }
.onEach { showValidationResult(it) }
4. 位置跟踪
kotlin
locationFlow
.throttleFirst(1000) // 1秒更新一次
.filter { it.accuracy < 50 } // 精度过滤
.distinctUntilChanged { old, new ->
distanceBetween(old, new) < 10 // 移动超过10米
}
.onEach { updateMap(it) }
六、注意事项
1. 执行上下文
kotlin
// 正确:在适当的上下文中使用
flow
.debounce(300)
.flowOn(Dispatchers.Default) // 防抖操作在后台线程
.collectOn(Dispatchers.Main) // 结果在主线程收集
// 避免在主线程进行长时间防抖
2. 取消处理
kotlin
flow
.debounce(300)
.onCompletion { println("Flow completed") }
.catch { println("Error: $it") }
.collect { /* ... */ }
3. 内存管理
kotlin
// SharedFlow 自动管理
val sharedFlow = someFlow
.debounce(300)
.shareIn(scope, replay = 1)
// StateFlow 保持最新值
val stateFlow = someFlow
.distinctUntilChanged()
.stateIn(scope, SharingStarted.Lazily, initialValue)
总结
| 操作符 | 最佳实践 | 常见错误 |
|---|---|---|
| debounce | 搜索输入、保存操作 | 时间设置过短/过长 |
| throttle | 滚动事件、按钮点击 | 与 debounce 混淆 |
| distinctUntilChanged | 状态更新、数据同步 | 忘记自定义比较器 |
合理组合这三个操作符,可以创建高效、响应迅速的用户体验,同时减少不必要的计算和网络请求。