29、Flow与传感器:传感器数据的Flow化、实时数据流处理、功耗优化
传感器,是Android设备和物理世界之间的桥梁。但说实话,原生的传感器API用起来挺别扭的——你得注册监听器、手动管理生命周期、还得小心别漏掉取消注册导致内存泄漏。我个人习惯是把传感器数据包装成Flow,这样整个数据流就变得优雅多了。
传感器数据的Flow化
先看一个最基础的例子。把加速度传感器包装成Flow:
class SensorFlowManager(private val sensorManager: SensorManager) {
fun accelerometerFlow(): Flow<SensorEvent> = callbackFlow {
val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
if (sensor == null) {
close(SensorException("设备不支持加速度传感器"))
return@callbackFlow
}
val listener = object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent?) {
event?.let { trySend(it) }
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
// 精度变化,一般忽略
}
}
sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
// 协程取消时自动清理
awaitClose {
sensorManager.unregisterListener(listener)
}
}
}
这里用了callbackFlow,它是专门为回调式API设计的。我在项目中遇到过一个问题:如果传感器返回数据太快,trySend可能会丢数据。解决办法是改用send配合缓冲:
fun accelerometerFlow(): Flow<SensorEvent> = callbackFlow {
// ... 同上
val listener = object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent?) {
event?.let {
// 使用send而不是trySend,配合缓冲
launch {
send(it)
}
}
}
}
// ...
}.buffer(Channel.CONFLATED) // 只保留最新值
CONFLATED缓冲策略很关键——它只保留最后一个值。对于传感器数据来说,旧数据没有意义,我们只关心最新的状态。
实时数据流处理
拿到传感器Flow之后,真正的乐趣才开始。你可以用Flow的操作符做各种实时处理。
采样降频:传感器通常每秒产生几十到几百次数据,但UI更新不需要那么快。
accelerometerFlow()
.sample(100) // 每100毫秒取一个样本
.collect { event ->
// 更新UI
}
过滤抖动:传感器数据有噪声,小幅度变化可能是干扰。
accelerometerFlow()
.debounce(50) // 50毫秒内没有新数据才发射
.collect { event ->
// 处理稳定后的数据
}
组合多个传感器:比如同时用加速度和陀螺仪做姿态检测。
val accelFlow = sensorManager.accelerometerFlow()
val gyroFlow = sensorManager.gyroscopeFlow()
accelFlow.combine(gyroFlow) { accel, gyro ->
// 融合计算姿态
val roll = calculateRoll(accel.values)
val pitch = calculatePitch(accel.values)
val yaw = integrateGyro(gyro.values)
OrientationData(roll, pitch, yaw)
}.collect { orientation ->
// 更新3D场景
}
我曾经做过一个AR导航项目,就是靠这种组合方式实现手机姿态跟踪的。一开始直接用原始数据,结果画面抖得厉害。后来加了debounce和sample,效果好了很多。
核心思路:传感器数据流处理的关键是「降噪」和「降频」。降噪靠debounce和filter,降频靠sample。两者配合使用效果最佳。
功耗优化
传感器是耗电大户。你想想看,如果GPS、加速度计、陀螺仪全开,手机电量撑不了多久。我见过不少App因为传感器没管理好,被用户投诉耗电。
策略一:选择合适的采样率
| 采样率常量 | 延迟(微秒) | 适用场景 |
|---|---|---|
| SENSOR_DELAY_NORMAL | 200,000 | 屏幕方向、简单手势 |
| SENSOR_DELAY_UI | 60,000 | UI动画、游戏 |
| SENSOR_DELAY_GAME | 20,000 | 高帧率游戏 |
| SENSOR_DELAY_FASTEST | 0 | 专业测量、VR |
别一上来就用FASTEST。我习惯先评估需求:如果只是检测手机是否翻转,NORMAL就够了。
策略二:按需注册和注销
Flow的awaitClose帮我们自动处理了注销。但要注意,如果页面不可见时还在收集传感器数据,那就是浪费。配合lifecycleScope和repeatOnLifecycle:
class SensorViewModel : ViewModel() {
private val sensorManager = SensorFlowManager(
getSystemService(Context.SENSOR_SERVICE) as SensorManager
)
fun startCollecting() {
viewModelScope.launch {
sensorManager.accelerometerFlow()
.collect { event ->
// 处理数据
}
}
}
}
// 在Activity或Fragment中
override fun onResume() {
super.onResume()
viewModel.startCollecting()
}
override fun onPause() {
super.onPause()
viewModel.stopCollecting() // 取消协程
}
策略三:使用批处理
Android 13+支持传感器批处理,可以把多个事件打包一起上报,减少CPU唤醒次数。
val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
// 设置最大报告延迟,允许批处理
sensorManager.registerListener(
listener,
sensor,
SENSOR_DELAY_NORMAL,
1000000 // 最大延迟1秒,允许批处理
)
注意:批处理会增加延迟。如果你的应用需要实时响应(比如游戏),不要设置太大的最大延迟值。
知识体系总览
下面这张图概括了传感器Flow化的完整链路:
我的经验:传感器开发最容易踩的坑是「忘记注销监听器」。用Flow+callbackFlow之后,这个问题基本解决了。但要注意,如果多个地方同时收集同一个传感器Flow,建议用shareIn共享数据流,避免重复注册传感器。
嗯,传感器Flow化其实就三件事:包装成Flow、用操作符处理、管好功耗。把这三步走稳了,你的传感器应用就能既流畅又省电。