25、LiveData与Sensor:传感器数据监听、加速度计数据展示、实战:计步器
传感器开发,在Android里一直是个「看起来简单,做起来坑多」的领域。尤其是加速度计,你想想看,手机放桌上不动,它还在输出数值——因为重力还在。很多新手第一次拿到的数据,直接懵了。
我个人习惯把传感器和LiveData搭配使用。为什么?因为传感器的数据是持续变化的,而LiveData天生就适合这种「观察者模式」。说白了,它就是为这种场景量身定做的。
传感器基础:加速度计到底在测什么?
加速度计测量的是三个轴上的加速度,单位是m/s²。但有个关键点:它测的是「相对于自由落体的加速度」。所以手机平放桌上时,Z轴会显示约9.8 m/s²——那是重力。
我在项目中遇到过一个问题:测试人员说「手机没动,步数却在涨」。排查了半天,原来是加速度计的阈值设得太低了。手机放桌上,轻微的震动都被当成了步数。
用LiveData封装传感器数据
直接使用SensorEventListener,代码会散落在Activity或Fragment里。我建议用LiveData把它包装起来,这样任何观察者都能优雅地订阅数据。
class AccelerometerLiveData(context: Context) : LiveData<SensorEvent>() {
private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
private val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
private val listener = object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent?) {
event?.let { postValue(it) }
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
// 精度变化时,我一般会打个日志
}
}
override fun onActive() {
sensorManager.registerListener(listener, accelerometer, SensorManager.SENSOR_DELAY_NORMAL)
}
override fun onInactive() {
sensorManager.unregisterListener(listener)
}
}
注意看,onActive() 和 onInactive() 是LiveData的生命周期回调。当有观察者开始观察时,自动注册监听;没有观察者时,自动注销。这比手动管理生命周期优雅太多了。
加速度计数据展示:从原始数据到可视化
拿到原始数据后,直接显示三个浮点数,用户看不懂。我一般会做两件事:
- 计算合加速度: sqrt(x² + y² + z²),然后减去重力(约9.8),得到「净加速度」。
- 做平滑处理: 用滑动平均或低通滤波,去掉高频噪声。
fun calculateNetAcceleration(event: SensorEvent): Float {
val x = event.values[0]
val y = event.values[1]
val z = event.values[2]
val raw = sqrt(x * x + y * y + z * z)
return raw - 9.8f // 减去重力
}
展示的时候,我习惯用三个进度条或三个圆形指示器,分别显示x、y、z轴的数值。颜色用红绿蓝区分,用户一眼就能看出手机的姿态。
实战:计步器的核心逻辑
计步器的原理,说白了就是检测「走路时的冲击波」。人走路时,合加速度会呈现一个波峰-波谷的周期性变化。我们只需要检测波峰,然后计数。
我曾经踩过一个坑:把阈值设成固定值。结果不同的人走路,步幅和力度完全不同。后来我改用动态阈值——根据最近一段时间的最大值和最小值,实时调整判断标准。
class StepCounterViewModel(context: Context) : ViewModel() {
private val accelerometerLiveData = AccelerometerLiveData(context)
private val _stepCount = MutableLiveData<Int>(0)
val stepCount: LiveData<Int> = _stepCount
private var lastPeakTime = 0L
private var lastAcceleration = 0f
private var threshold = 2.0f // 动态调整
fun startCounting() {
accelerometerLiveData.observeForever { event ->
val netAcc = calculateNetAcceleration(event)
val currentTime = System.currentTimeMillis()
// 检测波峰:当前值大于阈值,且比上一个值大
if (netAcc > threshold && netAcc > lastAcceleration) {
if (currentTime - lastPeakTime > 200) { // 200ms防抖
_stepCount.value = (_stepCount.value ?: 0) + 1
lastPeakTime = currentTime
}
}
// 动态更新阈值(取最近100ms内的最大值的一半)
// 这里简化处理,实际项目中用队列维护滑动窗口
lastAcceleration = netAcc
}
}
override fun onCleared() {
super.onCleared()
accelerometerLiveData.removeObserver { }
}
}
嗯,这里要注意:observeForever 不会自动注销,所以一定要在 onCleared() 里手动移除。否则ViewModel都销毁了,传感器还在跑,内存泄漏妥妥的。
知识体系总览
下面这张图,把本章的核心脉络梳理了一遍。从传感器硬件到LiveData封装,再到计步器业务逻辑,每一步都有它的设计考量。
总结一下
传感器开发,本质上就是「原始数据 → 特征提取 → 业务判断」的流水线。LiveData在这里扮演了数据管道的角色,让整个流程变得干净、可测试。
我个人觉得,计步器是理解传感器最好的入门项目。它不复杂,但涵盖了滤波、阈值、防抖这些通用技术。你把这个搞懂了,后面做手势识别、姿态检测,思路都是一样的。