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 定期采样显示
  • 日志记录:用时间窗口批量写入文件
相关推荐
祖国的好青年22 分钟前
VS Code 搭建 React Native 开发环境(Windows 实战指南)
android·windows·react native·react.js
黄林晴1 小时前
警惕!AGP 9.2 别只改版本号,R8 规则与构建链路全线收紧
android·gradle
小米渣的逆袭1 小时前
Android ADB 完全使用指南
android·adb
儿歌八万首1 小时前
Jetpack Compose Canvas 进阶:结合 animateFloatAsState 让自定义图形动起来
android·动画·compose
zhangphil2 小时前
Android Page 3 Flow读sql数据库媒体文件,Kotlin
android·kotlin
神探小白牙2 小时前
echarts,3d堆叠图
android·3d·echarts
李白的天不白3 小时前
如何项目发布到github上
android·vue.js
summerkissyou19873 小时前
Android-RTC、NTP 和 System Time(系统时间)
android
小书房3 小时前
Kotlin使用体验及理解1
android·开发语言·kotlin
撩得Android一次心动4 小时前
Android Navigation 组件全面讲解
android·jetpack·navigation