Перегрів пристрою через неправильну частоту опитування датчиків — типова проблема. Розряд батареї за годину замість дня. Ми стикалися з проектом, де sensor fusion видавав випадкові кватерніони на дешевих телефонах. Рішення — правильний вибір віртуальних датчиків та батч-оновлень. Більше 5 років досвіду та більше 20 проектів із датчиками руху — за нашими плечима.
Android Sensor Framework надає доступ до 13+ типів датчиків через єдиний SensorManager. Акселерометр та гіроскоп — апаратні. TYPE_LINEAR_ACCELERATION, TYPE_ROTATION_VECTOR, TYPE_GRAVITY — віртуальні: обчислюються з апаратних через sensor fusion у firmware. Різниця в тому, що віртуальні датчики споживають більше CPU при високій частоті та можуть бути недоступні на бюджетних пристроях. Детальніше — в Android Developer: Sensors Overview. Наш досвід показує: для точної орієнтації використовуйте TYPE_ROTATION_VECTOR, для лінійного прискорення — TYPE_LINEAR_ACCELERATION.
Реєстрація та життєвий цикл
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 в Android вимагає калібрування?
Датчики на дешевих пристроях мають зсуви та шум. Віртуальні датчики (TYPE_ROTATION_VECTOR) використовують вбудоване калібрування firmware, але на старих ядрах можуть дрейфувати. Ми тестуємо на апаратах різних вендорів та при необхідності додаємо свій фільтр (Mahony/Madgwick) для корекції.
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 коли потрібно вимірювати тільки динамічне прискорення.
Як реалізувати детекцію падіння?
- Використовуйте акселерометр із частотою 50 Гц.
- Застосуйте високочастотний фільтр для відділення гравітації:
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) ) - Реалізуйте логіку: якщо magnitude < 0.5g протягом >300 мс (вільне падіння), потім magnitude > 3g (удар) — надсилайте SOS.
Для носимих пристроїв (літні люди, шахтарі) цей метод чудово працює.
Як організувати фоновий моніторинг без перевитрати батареї?
Використовуйте батч-оновлення (maxReportLatencyUs) та JobScheduler для періодичної обробки. Віртуальні датчики у фоні краще не використовувати — вони споживають CPU постійно. Для простої детекції активності (кроки, спокій) достатньо акселерометра з частотою 5-10 Гц та батчем в 1 секунду (1000 мс).
Класифікація активностей
Паттерни: ходьба — регулярні піки 1.5–2.5 м/с² з частотою 1.5–2.5 Гц. Їзда — низькочастотні вібрації < 0.5 Гц. Спокій — magnitude < 0.1 м/с². Для класифікації використовуйте простий пороговий детектор або модель машинного навчання (наприклад, випадковий ліс) — це покращує точність до 95%.
Батч-оновлення (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 семплів). При переповненні старі дані витісняються — враховуйте при тривалих сесіях.
Що входить у роботу з інтеграції датчиків
- Аналіз — вибір датчиків під сценарій (навігація, трекінг активності, носимі пристрої)
- Архітектура — ViewModel/Service, обробка lifecycle, сенсорна оптимізація батареї
- Реалізація — реєстрація, фільтрація, класифікація подій (падіння, кроки, повороти)
- Тестування — на реальних пристроях (5+ моделей з різними чипсетами)
- Підтримка — документація API, навчання команди, гарантія на код до 6 місяців
Порівняння підходів до sensor fusion
| Характеристика | TYPE_ROTATION_VECTOR (вбудований) | Ручний Madgwick/Mahony |
|---|---|---|
| Точність на дорогих пристроях | ±1° | ±2-3° після калібрування |
| Споживання CPU | Низьке | Високе (оновлення 200+ раз/с) |
| Підтримка на бюджетних телефонах | Часто немає | Працює всюди |
| Складність інтеграції | Мінімальна | Потрібне налаштування параметрів |
Наш досвід: для 90% проектів вистачає вбудованих віртуальних датчиків. Ручний fusion потрібний тільки для AR/VR або роботи з застарілими SoC.
Терміни
Базова інтеграція 1–2 датчиків з обробкою даних — від 3 до 5 робочих днів. Багатодатчиковий алгоритм із класифікацією активностей, фоновим записом та аналітикою — від 2 до 4 тижнів. Вартість розраховується індивідуально після аналізу вимог. Часові рамки уточнюємо після аудиту вашого проекту.
Наша команда — більше 5 років досвіду та більше 20 успішних проектів із датчиками руху. Замовте консультацію — оцінимо feasibility та запропонуємо архітектуру під ваш сценарій. Отримайте якісну інтеграцію з гарантією на код до 6 місяців.
Детальніше про Sensor API читайте в офіційній документації Android.







