Синхронизация данных между мобильным приложением и часами
Синхронизация между телефоном и часами — это не просто «отправить JSON туда-обратно». Bluetooth-соединение прерывается, пользователь уходит в душ с часами без телефона, приложение на телефоне убивается системой. Данные должны прийти в нужном порядке, без дублей и без потерь — и при этом не сжирать батарею ни на одной из сторон.
Два разных мира: Wear OS и watchOS
На Wear OS — Wearable Data Layer API. Три канала для разных задач:
| Канал | Размер | Гарантия доставки | Когда использовать |
|---|---|---|---|
| DataClient | до 100 КБ | Да (при подключении) | Конфигурация, настройки, небольшие данные |
| MessageClient | любой | Нет | Команды реального времени |
| ChannelClient | без лимита | Да | Файлы, медиа, большие датасеты |
На watchOS — WatchConnectivity framework. WCSession.transferUserInfo() для фоновой доставки, sendMessage() для интерактивного обмена (только когда оба устройства доступны), transferFile() для файлов.
Ошибка, которую делают все: использовать sendMessage() там, где нужен transferUserInfo(). sendMessage требует активного соединения. Если часы в airplane mode — данные теряются. transferUserInfo ставит данные в очередь и доставляет при следующем подключении.
Conflict resolution и идемпотентность
Представьте: пользователь редактирует заметку на телефоне и на часах одновременно (в режиме офлайн). Оба устройства потом синхронизируются. Чьи данные правильные?
Без стратегии разрешения конфликтов — последний победит. Это плохо для любых данных кроме «показаний датчиков».
Строим синхронизацию с vector clock или упрощённым last-write-wins с timestamp. Каждое изменение получает updated_at в миллисекундах UTC и device_id. При конфликте — явная политика: для пользовательских данных спрашиваем, для телеметрии — берём с большим timestamp.
Идемпотентность обязательна: повторная доставка того же DataItem не должна создавать дубль в базе. На Wear OS DataClient сам дедуплицирует по пути (/data/workouts/123) — если данные не изменились, onDataChanged не вызывается.
Работа в фоне
На Android часовое приложение получает данные через WearableListenerService — он запускается системой даже если приложение не активно. Но с Android 13+ и Wear OS 4 добавились ограничения на запуск сервисов. WearableListenerService — исключение, он продолжает работать. Однако если в нём делать тяжёлые операции (запись в Room, сетевые запросы), нужен корутин scope, привязанный к жизненному циклу сервиса.
На iOS WCSession работает через application(_:didReceiveUserInfo:) делегат — он вызывается даже при фоновой доставке. Но обновлять UI напрямую нельзя — только через DispatchQueue.main.async.
Батарейная оптимизация. Каждая синхронизация — это Bluetooth wake-up. Группируем мелкие обновления через батчинг: вместо отправки каждого шага отдельно — агрегируем за 30 секунд и отправляем одним DataItem. На Wear OS используем PassiveMonitoringClient для сбора данных здоровья без постоянного wake lock.
Практический пример
Приложение для трекинга тренировок: часы собирают ЧСС каждую секунду, телефон хранит исторические данные и показывает графики.
Решение: на часах HealthServicesClient → ExerciseClient.prepareExercise() → агрегация каждые 5 секунд → MessageClient.sendMessage("/hr-batch", data). На телефоне WearableListenerService принимает батч → парсит → WorkManager записывает в Room → Flow обновляет UI.
При разрыве соединения часы буферизируют данные локально (Room на часах, до 10 МБ). При восстановлении — ChannelClient передаёт накопленное.
Сроки
Базовая двусторонняя синхронизация (конфигурация + небольшие данные): 1–2 недели. Полная система с офлайн-буфером, conflict resolution и health data: 3–5 недель. Стоимость зависит от объёма данных и требований к надёжности доставки.







