Разработка трекинга передвижения курьера в мобильном приложении
Трекинг курьера — это задача уровня сложности «кажется простой, пока не начнёшь». Показать иконку курьера на карте клиента и двигать её в реальном времени — технически это WebSocket, координаты, анимация маркера. Но продакшн-система включает: dead-reckoning при потере сигнала GPS, map snapping к дорогам, логику состояний курьера, батарейный компромисс на стороне приложения курьера и надёжную серверную шину.
Архитектура системы целиком
Три компонента:
- Приложение курьера — собирает GPS, передаёт координаты на сервер
- Сервер — принимает, хранит последнюю позицию, рассылает обновления подписчикам
- Приложение клиента — получает обновления, анимирует маркер
Это важно проектировать как систему, а не как «фичу в одном приложении». Выбор протокола передачи влияет на все три компонента.
Приложение курьера: сбор и передача координат
На Android — FusedLocationProviderClient из play-services-location в Foreground Service. Уведомление в статус-баре обязательно с Android 8+. Интервал обновления — компромисс: 5 секунд даёт хорошую точность, 15 секунд экономит батарею. Для пешего курьера — Priority.PRIORITY_HIGH_ACCURACY + 5 секунд. Для авто — Priority.PRIORITY_BALANCED_POWER_ACCURACY + 10 секунд с setMinUpdateDistanceMeters(20f): лишние точки при стоянии на светофоре не нужны.
На iOS — CLLocationManager с desiredAccuracy: kCLLocationAccuracyBestForNavigation в активном режиме и переключением на Significant Location Changes при уходе в фон (подробнее в статье по фоновому трекингу). activityType = .automotiveNavigation на iPhone активирует дополнительную фильтрацию шума GPS при движении на авто.
Фильтрация аномальных точек на клиенте — обязательна. GPS на городских улицах регулярно выдаёт скачки на 50-200 метров (multipath от зданий). Простой фильтр: отбрасываем точку, если horizontalAccuracy > 50 метров или расчётная скорость между двумя точками > 200 км/ч.
func shouldAcceptLocation(_ location: CLLocation) -> Bool {
guard location.horizontalAccuracy > 0,
location.horizontalAccuracy <= 50 else { return false }
if let lastLocation = lastAcceptedLocation {
let timeDelta = location.timestamp.timeIntervalSince(lastLocation.timestamp)
let distance = location.distance(from: lastLocation)
let impliedSpeed = distance / timeDelta // метры/секунду
if impliedSpeed > 55.6 { return false } // > 200 км/ч
}
return true
}
Буферизация в локальной БД + отправка пачками при наличии сети — стандартная схема. При потере сети точки накапливаются в Room / Core Data, WorkManager / background fetch отправляет их при восстановлении.
Сервер: шина реального времени
Для небольших нагрузок (до нескольких тысяч курьеров одновременно) — Socket.IO на Node.js или FastAPI с WebSocket. Клиент-курьер публикует координаты в топик courier/{id}/position, сервер рассылает подписчикам — приложениям клиентов, которые заказали этого курьера.
Для масштаба — MQTT broker (Mosquitto, EMQ X, AWS IoT Core). MQTT легче WebSocket по трафику, лучше переживает нестабильные мобильные соединения за счёт QoS-уровней и сессий с persist.
Хранение текущей позиции — Redis: SET courier:{id}:position "{lat,lon,timestamp}" с TTL 60 секунд. Если за минуту новых координат нет — курьер считается офлайн. История маршрута — TimescaleDB или InfluxDB для time-series данных.
Map snapping к дорогам. Сырые GPS-координаты курьера прыгают относительно дороги. Google Roads API snapToRoads или nearestRoads выравнивает трек по дорожному графу. Для высоконагруженных систем — OSRM self-hosted: бесплатно, быстрее (< 10 мс vs 50-100 мс у Google), без квот.
Приложение клиента: плавная анимация маркера
Получаем новую координату курьера через WebSocket/MQTT каждые 5-10 секунд. Без анимации маркер «прыгает» — некомфортно. Нужно интерполировать движение между двумя точками за N секунд.
На Android через ValueAnimator:
val animator = ValueAnimator.ofFloat(0f, 1f).apply {
duration = 3000
interpolator = LinearInterpolator()
addUpdateListener { animation ->
val fraction = animation.animatedFraction
val lat = prevLat + (newLat - prevLat) * fraction
val lon = prevLon + (newLon - prevLon) * fraction
marker.position = LatLng(lat, lon)
}
}
animator.start()
На iOS — CADisplayLink или UIView.animate с кастомным timing function. Угол поворота маркера-стрелки считаем через atan2(deltaLon, deltaLat) — курьер смотрит в направлении движения.
В Flutter — AnimatedWidget с Tween<LatLng> или TweenAnimationBuilder. Пакет google_maps_flutter не анимирует маркеры встроенно, но Marker(position: interpolatedPosition) в setState каждые 16 мс в Ticker даёт плавность 60 FPS.
Состояния курьера
Курьер не всегда едет. Схема состояний: idle → assigned → picking_up → delivering → completed. Каждое состояние влияет на UI клиента: «ищем курьера», «курьер едет к ресторану», «курьер везёт ваш заказ». Переходы состояний — серверная логика, не клиентская. Приложение курьера отправляет события (принял заказ, забрал, вышел к клиенту), сервер меняет состояние и нотифицирует клиента через тот же канал.
Процесс работы
Проектирование протокола и серверной архитектуры. Разработка приложения курьера с фоновым трекингом. Реализация серверной шины. Разработка UI для клиента с анимацией. Нагрузочное тестирование шины: 100/500/1000 одновременных курьеров.
Срок: от двух до четырёх недель в зависимости от количества платформ, наличия готового серверного API и требований к масштабированию.







