Реализация фоновой записи геолокации в мобильном приложении
На Xiaomi с MIUI 14 foreground service убит через 8 минут после выключения экрана. Трек оборвался. Пользователь думает, что приложение работает — уведомление в статусбаре есть, иконка есть. Но FusedLocationProviderClient перестал получать обновления, потому что процесс убит батарейным менеджером MIUI.
Это самая распространённая причина сломанного геотрекинга на Android — и у неё нет универсального решения. Есть набор мер, которые вместе дают приемлемый результат.
Android: как выжить в агрессивных лаунчерах
Foreground Service — необходимый минимум. Без него трекинг не живёт нигде. Сервис запускается с startForeground(id, notification), тип FOREGROUND_SERVICE_TYPE_LOCATION (обязателен с Android 10). Уведомление должно показывать текущий статус — «идёт запись» или текущую скорость.
Автозапуск на MIUI. com.miui.securitycenter → «Автозапуск» — при первом запуске показываем Intent, направляющий пользователя в настройки. Это единственный способ выжить на Xiaomi. Аналогично для Huawei: com.huawei.systemmanager → «Управление батареей» → «Запустить вручную». Список intent'ов по производителям — в библиотеке AutoStarter (Android).
WakeLock — не помогает одиночно. PARTIAL_WAKE_LOCK удерживает CPU, но не защищает процесс от kill на уровне MIUI/EMUI. Используем в паре с foreground service.
Конфигурация LocationRequest. Для трекинга человека пешком: interval = 10_000 мс, fastestInterval = 5_000 мс, priority = Priority.PRIORITY_HIGH_ACCURACY. Для транспортного трекинга: interval = 3_000 мс. Для фоновой записи маршрута без спешки: interval = 30_000 мс с Priority.PRIORITY_BALANCED_POWER_ACCURACY — в 3 раза меньше расход батареи.
WorkManager как watchdog. Запускаем PeriodicWorkRequest каждые 15 минут (минимальный интервал WorkManager). Если foreground service не работает — watchdog его перезапускает. Не идеальное решение, но добавляет надёжность.
iOS: проще, но со своими ограничениями
На iOS CLLocationManager с allowsBackgroundLocationUpdates = true + background mode location в entitlements работает надёжно. iOS не убивает location services в фоне. Но есть нюансы:
pausesLocationUpdatesAutomatically = false обязательно. Иначе iOS сама решит приостановить обновления «для экономии батареи», когда пользователь долго стоит на месте.
desiredAccuracy. kCLLocationAccuracyBest даёт 5–10 метров, но высасывает батарею. kCLLocationAccuracyNearestTenMeters — достаточно для большинства сценариев трекинга. kCLLocationAccuracyHundredMeters с distanceFilter = 50 — для простой записи «где был».
Significant Location Changes. startMonitoringSignificantLocationChanges() — это не трекинг, а «был в другом районе города». Срабатывает при смене соты (~300–500 метров). Подходит для логирования посещённых мест, не для непрерывного маршрута.
App termination. Если пользователь смахнул приложение из свайпера — трекинг прекращается. iOS не поднимет приложение автоматически через location. Решение: при получении applicationWillTerminate показываем предупреждение «закрытие приложения остановит запись маршрута».
Типичные ошибки реализации
| Ошибка | Последствие | Решение |
|---|---|---|
| Запись каждой точки в сеть отдельным HTTP-запросом | Высокий расход батареи, частые ошибки сети | Батч-буфер в памяти, отправка каждые 30 сек |
| Хранение трека только в памяти | Потеря данных при kill процесса | Персистентная очередь в SQLite |
PRIORITY_HIGH_ACCURACY без необходимости |
Батарея садится за 4–5 часов | Балансировать точность под сценарий |
Не запрашивать SCHEDULE_EXACT_ALARM (Android 12+) |
WorkManager watchdog работает неточно | Добавить permission и использовать AlarmManager |
Батч-отправка координат
Каждая точка GPS — это 3 числа + timestamp. Отдельный HTTP-запрос на каждую точку — расточительство. Буфер в памяти (или SQLite если нужна надёжность) с отправкой каждые N секунд или M точек:
// Android: накапливаем в ViewModel, отправляем батчем
private val locationBuffer = mutableListOf<LocationPoint>()
fun onLocationUpdate(location: Location) {
locationBuffer.add(location.toPoint())
if (locationBuffer.size >= BATCH_SIZE || isTimeToFlush()) {
sendBatch(locationBuffer.toList())
locationBuffer.clear()
}
}
На iOS аналогично через @Published var buffer: [CLLocation] в ObservableObject.
Итог
Надёжная фоновая геолокация — это не одна строка кода. Это foreground service + правильный LocationRequest + батч-буфер + watchdog + пользовательский онбординг с разрешениями на автозапуск. На iOS проще, на Android — больше edge-кейсов под конкретных производителей.
Реализация под один сценарий (пешеходный трекинг, автомобильный, полевые работы) занимает 3–5 рабочих дней. Если нужна кроссплатформенная реализация с учётом специфики всех Android-лаунчеров — около недели-двух.







