扩展函数与高阶函数在传感器中的应用:传感器监听的DSL封装、传感器数据的扩展函数、传感器校准的简化
传感器开发,说难不难,说简单也不简单。我早期做Android传感器项目时,最头疼的就是监听器的注册和注销——稍不留神就忘了释放,导致内存泄漏。后来用Kotlin的扩展函数和高阶函数重构了一遍,整个世界清净了。
今天我们就聊聊,怎么用Kotlin的特性,把传感器相关的代码写得既优雅又安全。
传感器监听的DSL封装
先看一个典型场景:监听加速度传感器。原生写法是这样的:
val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
sensorManager.registerListener(object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent?) {
event?.let {
// 处理数据
}
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
// 处理精度变化
}
}, accelerometer, SensorManager.SENSOR_DELAY_NORMAL)
这段代码有什么问题?
- 每次都要写匿名内部类,模板代码太多
- 容易忘记在onPause或onDestroy里注销监听
- 精度变化和传感器数据混在一起,职责不清晰
我习惯用DSL来封装。说白了,就是把那些重复的注册逻辑藏起来,让调用方只关心「我要监听什么」和「数据来了怎么办」。
class SensorDSL(private val context: Context) {
private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
private val listeners = mutableListOf<SensorEventListener>()
fun sensor(
type: Int,
delay: Int = SensorManager.SENSOR_DELAY_NORMAL,
onSensorChanged: (SensorEvent) -> Unit,
onAccuracyChanged: ((Sensor, Int) -> Unit)? = null
) {
val sensor = sensorManager.getDefaultSensor(type) ?: return
val listener = object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent?) {
event?.let { onSensorChanged(it) }
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
if (sensor != null) {
onAccuracyChanged?.invoke(sensor, accuracy)
}
}
}
sensorManager.registerListener(listener, sensor, delay)
listeners.add(listener)
}
fun unregisterAll() {
listeners.forEach { sensorManager.unregisterListener(it) }
listeners.clear()
}
}
调用起来就清爽多了:
val dsl = SensorDSL(this)
dsl.sensor(
type = Sensor.TYPE_ACCELEROMETER,
delay = SensorManager.SENSOR_DELAY_UI
) { event ->
val x = event.values[0]
val y = event.values[1]
val z = event.values[2]
// 处理加速度数据
}
嗯,这里要注意:unregisterAll()一定要在Activity的onPause()里调用。我曾经有个项目,就是因为忘了注销监听,导致传感器一直工作,电量哗哗地掉。
传感器数据的扩展函数
传感器返回的SensorEvent里,values是个float数组。不同传感器,数组的含义不同。比如加速度传感器,values[0]是X轴,values[1]是Y轴,values[2]是Z轴。每次都要写event.values[0],既啰嗦又容易下标越界。
扩展函数就是干这个的:
fun SensorEvent.accelerometerValues(): Triple<Float, Float, Float> {
return Triple(values[0], values[1], values[2])
}
fun SensorEvent.gyroscopeValues(): Triple<Float, Float, Float> {
return Triple(values[0], values[1], values[2])
}
fun SensorEvent.lightValue(): Float {
return values[0]
}
fun SensorEvent.proximityValue(): Float {
return values[0]
}
fun SensorEvent.pressureValue(): Float {
return values[0]
}
用起来就变成了:
dsl.sensor(type = Sensor.TYPE_ACCELEROMETER) { event ->
val (x, y, z) = event.accelerometerValues()
// 直接使用x, y, z,不用再记下标了
}
你想想看,如果项目里用了五六个传感器,每个都写values[0],代码会变成什么样?扩展函数把「数据含义」和「数据获取」解耦了,维护起来舒服很多。
SensorExtensions.kt文件里。这样团队里其他人一看就知道,传感器数据怎么取、有哪些辅助方法。
传感器校准的简化
传感器校准是个容易被忽略的点。比如陀螺仪,刚启动时会有漂移。如果不做校准,拿到的数据直接就用,结果会偏差很大。
我做过一个AR导航项目,陀螺仪数据不准,导致虚拟箭头一直乱飘。后来加了个校准步骤,问题就解决了。
用高阶函数来实现校准逻辑,非常优雅:
class SensorCalibrator(private val sampleSize: Int = 50) {
private val samples = mutableListOf<FloatArray>()
fun calibrate(
sensorData: () -> FloatArray,
onCalibrating: (progress: Float) -> Unit,
onCalibrated: (offset: FloatArray) -> Unit
) {
samples.clear()
var count = 0
while (count < sampleSize) {
val data = sensorData()
samples.add(data)
count++
onCalibrating(count.toFloat() / sampleSize)
Thread.sleep(10) // 简单采样间隔
}
val offset = FloatArray(samples[0].size) { axisIndex ->
samples.map { it[axisIndex] }.average().toFloat()
}
onCalibrated(offset)
}
fun applyCalibration(rawData: FloatArray, offset: FloatArray): FloatArray {
return rawData.mapIndexed { index, value -> value - offset[index] }.toFloatArray()
}
}
使用的时候:
val calibrator = SensorCalibrator(sampleSize = 30)
calibrator.calibrate(
sensorData = { currentRawData },
onCalibrating = { progress ->
// 显示校准进度条
progressBar.progress = (progress * 100).toInt()
},
onCalibrated = { offset ->
// 保存校准偏移量
calibratedOffset = offset
Toast.makeText(this, "校准完成", Toast.LENGTH_SHORT).show()
}
)
知识体系总览
下面这张图,把今天讲的内容串起来了:
说白了,Kotlin的扩展函数和高阶函数,就是帮我们把「脏活累活」藏起来。传感器开发本身不复杂,复杂的是那些重复的注册、注销、下标取值、校准逻辑。用DSL封装监听,用扩展函数包装数据,用高阶函数抽象校准——这三板斧下去,代码质量立马上一个台阶。
我记得有一次做运动检测App,用了这套封装后,传感器相关的代码从300行缩减到80行。而且后来换传感器硬件,只需要改扩展函数里的下标映射,其他地方完全不用动。这就是封装的力量。