Реализация датчиков движения в Android-приложении
Android Sensor Framework предоставляет доступ к 13+ типам датчиков через единый SensorManager. Акселерометр и гироскоп — аппаратные. TYPE_LINEAR_ACCELERATION, TYPE_ROTATION_VECTOR, TYPE_GRAVITY — виртуальные: вычисляются из аппаратных через sensor fusion в firmware. Разница в том, что виртуальные датчики потребляют больше CPU при высокой частоте и могут быть недоступны на бюджетных устройствах.
Регистрация и жизненный цикл
class SensorViewModel(application: Application) : AndroidViewModel(application) {
private val sensorManager = application.getSystemService(SENSOR_SERVICE) as SensorManager
private val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
private val gyroscope = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
private val _sensorData = MutableStateFlow<SensorData>(SensorData.Empty)
val sensorData: StateFlow<SensorData> = _sensorData.asStateFlow()
private val sensorEventListener = object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent) {
when (event.sensor.type) {
Sensor.TYPE_ACCELEROMETER -> handleAccelerometer(event.values)
Sensor.TYPE_GYROSCOPE -> handleGyroscope(event.values)
}
}
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {}
}
fun startListening() {
accelerometer?.let {
sensorManager.registerListener(
sensorEventListener, it,
SensorManager.SENSOR_DELAY_GAME // ~20ms
)
}
}
fun stopListening() {
sensorManager.unregisterListener(sensorEventListener)
}
override fun onCleared() {
stopListening()
}
}
Ключевое: unregisterListener строго в onCleared() ViewModel или в onPause() Activity. Забытый listener работает в фоне, жрёт батарею и может крашить приложение при уничтожении Activity.
Частоты опроса: константы и реальность
| Константа | Номинальная задержка | Реальная частота |
|---|---|---|
SENSOR_DELAY_NORMAL |
200 мс | ~5 Гц |
SENSOR_DELAY_UI |
60 мс | ~16 Гц |
SENSOR_DELAY_GAME |
20 мс | ~50 Гц |
SENSOR_DELAY_FASTEST |
0 мс | максимум железа |
С Android API 9 можно задавать произвольный интервал в микросекундах через registerListener(listener, sensor, samplingPeriodUs). Например, 10000 мкс = 100 Гц.
SENSOR_DELAY_FASTEST на Snapdragon 8 Gen 2 даёт до 500 Гц на акселерометре — это нужно только для специализированных приложений (анализ вибраций, балансировка). Для большинства задач 50–100 Гц достаточно.
Виртуальные датчики и sensor fusion
TYPE_ROTATION_VECTOR — кватернион ориентации устройства, вычисленный из акселерометра + гироскопа + магнитометра. Точнее чем делать fusion вручную:
val rotationVector = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
// В onSensorChanged:
val rotationMatrix = FloatArray(9)
SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values)
val orientationAngles = FloatArray(3)
SensorManager.getOrientation(rotationMatrix, orientationAngles)
val azimuth = Math.toDegrees(orientationAngles[0].toDouble()) // 0-360° от севера
val pitch = Math.toDegrees(orientationAngles[1].toDouble()) // -90 до +90°
val roll = Math.toDegrees(orientationAngles[2].toDouble()) // -180 до +180°
TYPE_LINEAR_ACCELERATION — акселерометр без гравитации (аналог userAcceleration в iOS CoreMotion). Используем вместо TYPE_ACCELEROMETER когда нужно измерять только динамическое ускорение.
Детекция конкретных событий
Определение ходьбы vs езды
val gravity = FloatArray(3)
val linearAccel = FloatArray(3)
val alpha = 0.8f
// В onSensorChanged для TYPE_ACCELEROMETER:
gravity[0] = alpha * gravity[0] + (1 - alpha) * event.values[0]
gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1]
gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2]
linearAccel[0] = event.values[0] - gravity[0]
linearAccel[1] = event.values[1] - gravity[1]
linearAccel[2] = event.values[2] - gravity[2]
val magnitude = sqrt(
linearAccel[0].pow(2) + linearAccel[1].pow(2) + linearAccel[2].pow(2)
)
Паттерны: ходьба — регулярные пики 1.5–2.5 м/с² с частотой 1.5–2.5 Гц. Езда — низкочастотные вибрации < 0.5 Гц. Покой — magnitude < 0.1 м/с².
Детекция падения
Для носимых устройств (пожилые люди, шахтёры): свободное падение → magnitude < 0.5g в течение > 300 мс → удар → magnitude > 3g. Это триггер для отправки SOS.
Батч-обновления (Android 4.4+)
SensorManager.flush() и параметр maxReportLatencyUs в registerListener позволяют накапливать данные в hardware FIFO и получать пачками. Полезно для фоновых приложений — сенсор копит данные, пока CPU спит, просыпается раз в N секунд, отдаёт всё сразу:
sensorManager.registerListener(
listener,
accelerometer,
10_000, // samplingPeriodUs = 100 Гц
500_000 // maxReportLatencyUs = 500 мс batch
)
Объём FIFO у разных чипсетов разный (512–4096 семплов). При переполнении старые данные вытесняются — учитываем при длительных сессиях.
Сроки
Базовая интеграция 1–2 датчиков с обработкой данных — 3–5 рабочих дней. Многодатчиковый алгоритм с классификацией активностей, фоновой записью и аналитикой — 2–4 недели.







