android fow 限流

🔄 两种限流方式对比

方式 目标 典型操作符 效果
流速控制 限制生产者速率 conflate(), buffer() 控制数据产生速度
处理频率控制 限制消费者处理频率 debounce(), throttleLatest() 控制数据处理速度

🎯 1. 流速控制(生产者侧)

这是您提到的场景:在收集端通过 delay 控制 。但这种方法不推荐,因为它阻塞了整个收集管道。

kotlin 复制代码
// ❌ 不推荐:在 collect 中 delay 会阻塞整个流
flow {
    for (i in 1..10) {
        emit(i)
        delay(10) // 生产者每10ms生产一个
    }
}
.collect { value ->
    delay(100) // ❌ 这样会阻塞整个流,导致缓冲区积压
    println("消费: $value")
}

更好的生产者限流方式:

a) 在源头控制速率

kotlin 复制代码
fun rateLimitedFlow(): Flow<Int> = flow {
    for (i in 1..100) {
        emit(i)
        delay(100) // ⭐ 在源头控制:每100ms最多生产一个
    }
}

// 消费者可以正常消费
rateLimitedFlow().collect { value ->
    // 不需要额外 delay
    println("消费: $value")
}

b) 使用 throttleLatest(Kotlin 1.7+)

kotlin 复制代码
import kotlinx.coroutines.flow.*

suspend fun throttleLatestExample() {
    flow {
        repeat(50) { i ->
            emit(i)
            delay(50) // 每50ms生产一个
        }
    }
    .throttleLatest(200) // ⭐ 每200ms取最新值,丢弃中间的
    .collect { value ->
        println("[${System.currentTimeMillis()}] 处理: $value")
        // 输出频率被限制为每200ms最多一次
    }
}

🕒 2. 处理频率控制(消费者侧)

这才是真正的**"限流",控制处理的频率**而不是阻塞管道。

a) debounce() - 防抖(等待稳定)

kotlin 复制代码
// 场景:搜索框输入,用户停止输入300ms后才触发搜索
searchQueryFlow
    .debounce(300) // ⭐ 300ms内没有新值才发出最后一个
    .collect { query ->
        performSearch(query)
    }

// 示例:快速输入"hello"
// 输入: h(0ms) e(50ms) l(100ms) l(150ms) o(200ms)
// 输出: 只在 500ms 时收到 "hello"(最后o输入后300ms)

b) sample() - 采样(定期取样)

kotlin 复制代码
// 场景:实时图表更新,每100ms采样一次
sensorFlow
    .sample(100) // ⭐ 每100ms取一个样本(时间点最近的值)
    .collect { data ->
        updateChart(data) // 每100ms更新一次,不管传感器多快
    }

// 更完整的示例:
fun samplingExample() {
    val startTime = System.currentTimeMillis()
    
    flow {
        repeat(20) { i ->
            emit(i)
            delay(30) // 每30ms产生一个值
        }
    }
    .sample(100) // 每100ms采样一次
    .collect { value ->
        val elapsed = System.currentTimeMillis() - startTime
        println("[$elapsed ms] 采样值: $value")
        // 输出:
        // [100 ms] 采样值: 2
        // [200 ms] 采样值: 5
        // [300 ms] 采样值: 8
        // [400 ms] 采样值: 11
    }
}

c) 基于时间的自定义限流

kotlin 复制代码
fun Flow<Int>.customThrottle(windowMillis: Long): Flow<Int> = flow {
    var lastEmitTime = 0L
    
    collect { value ->
        val currentTime = System.currentTimeMillis()
        if (currentTime - lastEmitTime >= windowMillis) {
            emit(value)
            lastEmitTime = currentTime
        }
        // 否则丢弃这个值
    }
}

// 使用
fastFlow
    .customThrottle(100) // 每100ms最多处理一个
    .collect { value ->
        processValue(value)
    }

📊 3. 综合实战:车载信号处理

在您的车载空调App中,不同信号需要不同的限流策略:

kotlin 复制代码
class VehicleSignalProcessor {
    
    // 1. 温度传感器数据(高频,只需定期采样)
    fun processTemperature(sensorFlow: Flow<Float>): Flow<Float> {
        return sensorFlow
            .sample(1000) // 每秒采样一次(温度变化慢)
            .conflate()    // 合并中间值,取最新
    }
    
    // 2. 用户按键事件(需要防抖)
    fun processButtonEvents(buttonFlow: Flow<ButtonEvent>): Flow<ButtonEvent> {
        return buttonFlow
            .debounce(50)  // 50ms防抖,防止误触
            .distinctUntilChanged() // 去重
    }
    
    // 3. CAN总线数据(需要时间窗口统计)
    fun processCanMessages(canFlow: Flow<CanMessage>): Flow<List<CanMessage>> {
        return canFlow
            .buffer(capacity = 100) // 缓冲100条
            .windowed(size = 10, timeMillis = 100) // ⭐ 每100ms的10条数据作为一个批次
            .map { batch ->
                // 批量处理CAN消息
                batch.filter { it.isValid() }
            }
    }
    
    // 4. 基于自定义时间窗口的限流
    fun processWithTimeWindow(): Flow<Int> {
        val flow = flow {
            repeat(100) { i ->
                emit(i)
                delay((10..50).random().toLong()) // 随机间隔10-50ms
            }
        }
        
        return flow
            .customTimeWindow(windowSize = 5, timeMillis = 100) // 100ms内的5个值
            .map { window ->
                // 处理时间窗口内的数据
                window.average().toInt()
            }
    }
}

// 自定义:时间窗口操作符
fun <T> Flow<T>.customTimeWindow(
    windowSize: Int,
    timeMillis: Long
): Flow<List<T>> = channelFlow {
    val buffer = mutableListOf<T>()
    var windowStartTime = System.currentTimeMillis()
    
    collect { value ->
        buffer.add(value)
        
        val currentTime = System.currentTimeMillis()
        val isWindowFull = buffer.size >= windowSize
        val isTimeElapsed = currentTime - windowStartTime >= timeMillis
        
        if (isWindowFull || isTimeElapsed) {
            send(buffer.toList())
            buffer.clear()
            windowStartTime = currentTime
        }
    }
    
    // 发送剩余数据
    if (buffer.isNotEmpty()) {
        send(buffer.toList())
    }
}

🎮 4. 可视化的限流效果

让我们通过一个具体例子看不同限流策略的效果:

kotlin 复制代码
suspend fun visualizeThrottling() {
    val sourceFlow = flow {
        repeat(30) { i ->
            emit(i)
            delay(20) // 每20ms一个值,持续600ms
        }
    }
    
    println("=== 原始数据流 ===")
    val startTime = System.currentTimeMillis()
    sourceFlow.collect { value ->
        val elapsed = System.currentTimeMillis() - startTime
        println("[${elapsed}ms] 原始: $value")
    }
    
    println("\n=== 使用 sample(100ms) ===")
    sourceFlow
        .sample(100)
        .collect { value ->
            val elapsed = System.currentTimeMillis() - startTime
            println("[${elapsed}ms] 采样: $value") 
            // 大约在 100ms, 200ms, 300ms... 输出
        }
    
    println("\n=== 使用 debounce(50ms) ===")
    sourceFlow
        .debounce(50)
        .collect { value ->
            val elapsed = System.currentTimeMillis() - startTime
            println("[${elapsed}ms] 防抖: $value")
            // 只会在流结束后输出最后一个值 29
        }
}

输出效果对比:

复制代码
=== 原始数据流 ===
[20ms] 原始: 0
[40ms] 原始: 1
[60ms] 原始: 2
... (每20ms一个)

=== 使用 sample(100ms) ===
[100ms] 采样: 4    ← 100ms时最近的值是第4个
[200ms] 采样: 9    ← 200ms时最近的值是第9个
[300ms] 采样: 14   ← 以此类推

=== 使用 debounce(50ms) ===
[650ms] 防抖: 29   ← 最后一个值发出后50ms才输出

🎯 实战选择指南

场景 推荐策略 代码示例
UI输入防抖 debounce(300) searchFlow.debounce(300)
传感器数据 sample(interval) sensorFlow.sample(100)
实时图表 时间窗口平均 .windowed(10, 1000).map { it.average() }
网络请求队列 固定速率 使用 delay 在源头控制
车载CAN信号 批次处理 .buffer().windowed(size, time)

关键点

  1. debounce :等待操作结束(如用户停止输入)
  2. sample :定期取样(如定时刷新UI)
  3. 时间窗口批量处理固定时间段内的数据
  4. 源头控制:在生产端控制速率(最根本的限流)

在您的车载空调App中,对于DDS信号:

  • 控制指令 :用 debounce 防止频繁发送
  • 状态更新 :用 sample 定期采样显示
  • 日志记录:用时间窗口批量写入文件
相关推荐
冬奇Lab3 小时前
Android 15 显示子系统深度解析(二):图形缓冲区管理与HWC硬件合成
android
wings专栏3 小时前
Android触摸事件分发记录
android
aaajj4 小时前
【Android】声控拍照例子
android
stevenzqzq4 小时前
Android MVI 中 setState(reduce: State.() -> State) 设计说明文档
android·mvi框架
鸣弦artha4 小时前
Flutter框架跨平台鸿蒙开发——InheritedWidget基础使用-计数器案例
android·flutter·harmonyos
嵌入式-老费4 小时前
Android开发(开发板的三种操作系统)
android
凛_Lin~~5 小时前
安卓网络框架——OkHttp源码解析(基于3.14.x)
android·网络·okhttp
stevenzqzq5 小时前
android SharedFlow和Channel比较
android·channel·sharedflow
zhangphil6 小时前
Kotlin实现Glide/Coil图/视频加载框架(二)
android·kotlin