Реалізація датчиків руху в 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 коли потрібно вимірювати тільки динамічне прискорення.
Детекція конкретних подій
Розрізнення ходьби та їзди
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 Гц. Спокій — величина < 0.1 м/с².
Детекція падіння
Для носимих пристроїв (людина похилого віку, шахтарі): вільне падіння → величина < 0.5g протягом > 300 мс → удар → величина > 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 тижні.







