Реализация подсчета шагов в мобильном приложении
Казалось бы, шаги — самая простая метрика. На деле: iOS и Android считают шаги по-разному, телефон в кармане и в руке даёт разные паттерны акселерометра, а задваивание данных между HealthKit и Google Fit — классическая жалоба пользователей в отзывах.
Два подхода: системный шагомер vs собственный алгоритм
Системный шагомер (рекомендуется)
iOS: CMPedometer — самый надёжный вариант. Считает шаги на уровне сопроцессора Motion Coprocessor (M-серия), не требует постоянной работы приложения:
let pedometer = CMPedometer()
guard CMPedometer.isStepCountingAvailable() else { return }
// Исторические данные
pedometer.queryPedometerData(from: startDate, to: endDate) { data, error in
guard let data = data else { return }
print("Шаги: \(data.numberOfSteps)")
print("Дистанция: \(data.distance ?? 0) м")
print("Этажи вверх: \(data.floorsAscended ?? 0)")
}
// Живые обновления
pedometer.startUpdates(from: Date()) { data, error in
DispatchQueue.main.async {
self.stepCount = data?.numberOfSteps.intValue ?? 0
}
}
CMPedometer.startUpdates() продолжает работать даже когда приложение в фоне — данные накапливаются и приходят при следующем открытии. Батарея не тратится на высокочастотный опрос — всё на уровне железа.
Android: TYPE_STEP_COUNTER и TYPE_STEP_DETECTOR.
TYPE_STEP_COUNTER — накопительный счётчик с момента последней перезагрузки устройства. Сбрасывается при reboot. Нужно хранить базовое значение при первом запуске дня.
TYPE_STEP_DETECTOR — событие на каждый шаг. Для подсчёта в реальном времени.
val sensorManager = getSystemService(SENSOR_SERVICE) as SensorManager
val stepSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
val stepListener = object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent) {
val totalSteps = event.values[0].toLong()
// Вычитаем базовое значение, полученное при первом запуске
val todaySteps = totalSteps - baseStepCount
updateUI(todaySteps)
}
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {}
}
sensorManager.registerListener(stepListener, stepSensor, SensorManager.SENSOR_DELAY_NORMAL)
SENSOR_DELAY_NORMAL — достаточно для шагомера. SENSOR_DELAY_FASTEST здесь бессмысленен и убивает батарею.
Собственный алгоритм (когда нужен)
Системный шагомер не работает на некоторых бюджетных Android-устройствах без TYPE_STEP_COUNTER (редко, но встречается). В этом случае — Peak Detection на акселерометре:
- Читаем
TYPE_ACCELEROMETERна 25 Гц - Вычисляем magnitude:
sqrt(x² + y² + z²) - Применяем low-pass фильтр:
filtered = alpha * raw + (1 - alpha) * prev(alpha ≈ 0.1) - Детектируем пик:
filtered > threshold(обычно 10.5–11.5 м/с²) после перехода через baseline - Минимальный интервал между шагами: 250–400 мс
Точность собственного алгоритма — 85–92% против 98%+ системного. Для большинства фитнес-приложений системный достаточен.
Интеграция с HealthKit / Health Connect
Шаги нужно писать в платформенное хранилище, иначе они не появятся в системном приложении «Здоровье» (iOS) или Health Connect (Android).
iOS — запись в HealthKit:
let stepType = HKQuantityType(.stepCount)
let stepSample = HKQuantitySample(
type: stepType,
quantity: HKQuantity(unit: .count(), doubleValue: Double(steps)),
start: periodStart,
end: periodEnd
)
healthStore.save(stepSample) { success, error in }
Android — Health Connect:
val stepsRecord = StepsRecord(
startTime = periodStart,
startZoneOffset = ZoneOffset.UTC,
endTime = periodEnd,
endZoneOffset = ZoneOffset.UTC,
count = steps
)
healthConnectClient.insertRecords(listOf(stepsRecord))
Проблема задваивания
Если телефон передаёт данные в Google Fit, а приложение пишет их ещё и в Health Connect — пользователь видит двойное количество шагов в системных приложениях. Решение: не писать шаги самостоятельно, если разрешено чтение из системного шагомера. Читаем из системного источника, агрегируем, показываем в своём UI, в HealthKit/Health Connect НЕ пишем (или пишем с уникальным source identifier и предупреждаем пользователя о возможном дублировании).
Сроки
Реализация счётчика шагов с системным шагомером на одной платформе — 2–4 рабочих дня. С интеграцией в HealthKit/Health Connect, фоновой синхронизацией и виджетом — до 2 недель.







