Используемые устройства — телефон Samsung Galaxy S8+ (SM-G955FD), часы Galaxy Watch 5 Pro (P0ED)
Я столкнулся с проблемой зависания данных на часах, что стало проблемой, особенно когда речь идет о занятии сопртом. Мне нравится велосипед и во время тренировки удобно отсматривать показатели только одной рукой и управление спортивным режимом (в этом форке он нами так же добавлен в основное приложение) желательно только на часах.
Дополнено включение и настройка спортивного режима не только в app, но и в wear, а так же была исследована проблема залипания данных (когда на часы данные о глюкозе приходят не вместе с обновлением на телефоне, а гораздо позже или периодически например всего несколько раз в час, не стабильно). При этом сейчас данные приходят как правило быстрее чем за 7 минут (на часы). Если быть точным - обновление и показ происходит не позже 7 минут.
Ниже показываю что происходит внутри программы, какие события удалось отследить в логах (некоторые логи были добавлены мной) и как диагностировал проблему.
GitHub и форк
Работа велась в рамках проекта (форка) ? OpenApsAIMI-additional-sport-options: https://github.com/AlexeyDedeshko/OpenApsAIMI-additional-sport-options, изменения уже в master.
Дальнейшая доработка будет в wear-dev.
Особенность форка:
В нем добавлены возможности управления спортивным режимом с часов (да, спортивный режим добавлен и в основное приложение), так же переработан самый простой из имеющихся циферблат для Wear OS - как раз для улучшения стабильности связи. Внутри программы есть несколько циферблатов но мы наладили связь только для CircleWatchface. При выборе на часах он называется "(круглый)".
Цель — минимальная задержка обновления данных.
То есть это отладочный «лайт»-вариант, который позволил проверить стабильность канала данных (DataLayer → RxBus → Watchface). Внимание, на нем пока не отображаются корерктно другие данные кроме глюкозы, работа над этими данными ведется.
Основные понятия
DataLayer
Это канал связи между телефоном и часами на Wear OS. Телефон отправляет события (например, новые показания сахара), а часы принимают их. В логах мы видим работу этого канала с префиксом: WEAR_PIPE ...
onMessageReceived
Метод сервиса DataLayerListenerServiceWear на часах. Срабатывает, когда по DataLayer пришло сообщение с телефона.
Каждый раз, когда телефон отправляет новые данные на часы через DataLayer, на часах срабатывает метод onMessageReceived.
«Соседние» записи onMessageReceived — это две подряд идущие такие строки в логе. Между ними обычно проходит ~1 минута (т.к. новые данные глюкозы от сенсора приходят раз в 5 мин, но по DataLayer могут пересылаться чаще). Если же интервал между соседними записями >7 минут → значит, телефон не отправлял новые данные на часы (или часы их не принимали). Это и есть «залипание» на уровне канала. Залипание в реальности вызвано не только этим, но этот момент отработан.
-
onMessageReceived получил данные (например, одно значение сахара). Но нарисовать их на экране он не может напрямую — этим занимается другой компонент (циферблат). Чтобы «раздать» данные всем заинтересованным частям программы, они кладутся на RxBus (в логах WEAR_PIPE postToRxBus type=SingleBg)
Тут мы видим, какой именно тип события отправлен:
SingleBg → одно измерение глюкозы,
Status → статус (IOB, BGI, насос),
GraphData → пачка точек для графика.
RxBus
Это внутренняя «шина событий» в приложении.
Когда DataLayer получает данные, он не рисует их напрямую, а передаёт их через RxBus.
Так часы могут подписывать разные компоненты на одни и те же данные: и циферблат, и виджеты, и сервисы.
postToRxBus
Наша метка перед тем, как отправить данные во внутреннюю шину событий RxBus.
Это значит: «Данные пришли и теперь я раздаю их всем подписчикам (включая циферблат)».
➝ Лог: WEAR_PIPE postToRxBus type=SingleBg ...
WEAR_FACE Rx SingleBg
Это лог внутри циферблата (класса CircleWatchface). Когда RxBus «выкинул» событие, циферблат и получает данные. Мы логируем момент прихода.
Watchface (циферблат)
Это класс (у нас CircleWatchface), который отрисовывает данные на экране часов.
У него есть метод:
onDraw(Canvas) — собственно момент прорисовки. Каждый раз, когда нужно обновить экран, вызывается этот метод.
+Xms after invalidate()
Пишется в onDraw() циферблата. Показывает, сколько миллисекунд прошло от invalidate() (запрос перерисовки) до реального вызова onDraw() (отрисовки на экране).
➝ Лог: WEAR_FACE: onDraw(); +5ms after invalidate()
В логах он фиксируется как: WEAR_FACE: onDraw(); +5ms after invalidate()
События SingleBg / Status / GraphData
-
SingleBg — одно измерение уровня сахара (Single Blood Glucose).
То самое число, которое рисуется крупным шрифтом.
В логе выглядит так: WEAR_FACE: Rx SingleBg at 123456789ms
Status — сопутствующие данные (например, IOB — активный инсулин, BGI, состояние насоса). Рисуются мелким текстом или могут быть отключены.
GraphData — пачка точек для графика за последние часы. Мы их в упрощённом циферблате не рисуем, но они тоже приходят.
invalidate() → onDraw() — Когда циферблат получает новые данные, он вызывает invalidate(). Это значит: «экран устарел, нужно перерисовать». Дальше система вызывает onDraw(Canvas) — и мы реально рисуем новые цифры.
? Описание процесса обмена данными
Ниже я своими словами описываю процесс обмена данными, описание может быть не точным и приведено чтобы заложить основу "простого понимания" процесса обмена данными. Я буду рад если вы скорректируете меня в комментариях.
Все начинается со "шпиона" в нашем теле - это сенсор глюкозы (в моем случае Dexcom 6 серии). Он получает данные и отправляет в xDrip, которые передает данные в AAPS.
AAPS выступает опытным почтальоном, который оборачивает данные в специальный формат (кладет записи в капсулу) и отправляет на часы с определенной переодичностью.
DataLayer (Layer - это в переводе слой, прослойка или несушка) это как труба в которую и кладется капсула и по этой трубе несется на часы. В логах это "WEAR_PIPE: onMessageReceived".
Капсулу принимает не сразу экран, а она как бы попадает в громкоговоритель на площади, называемый RxBus. RxBus громко читает послание и тогда любой житель (например, циферблат, виджет или другой сервис) может услышать и использовать эти данные. В логах это так: WEAR_FACE: Rx SingleBg at 123456789ms = на площади прозвучало новое значение сахара.
Циферблат - это художник, как только он слышит новое объявление о глюкозе на площади - он отрисовывает его на экране. Логика художника такая, сначала ему говорят - твой рисонок устарел, перерисуй - это команда invalidate(). Потом художник берет кисточку и рисует на холсте (onDraw(Canvas)). В логах WEAR_FACE: onDraw(); +5ms after invalidate().
Если данные дошли до RxBus и циферблата, отрисовка занимает несколько миллисекунд.
Итог: задержка может быть только на этапе пересылки с телефона на часы (казалось мне по началу). Но в итоге она может быть и на этапе отрисовки когда отрисовать не получается ввиду того что андроид не дает перерисовать когда часы спят и нам нужно их будить. Это мы тоже полечили.
Схематично так
[SENSOR]
└─ измеряет сахар (каждые N минут)
▼
[PHONE / МОБ. ПРИЛОЖЕНИЕ]
└─ формирует событие с данными
▼
[DataLayer] ← «труба» телефон→часы
(в логах: WEAR_PIPE onMessageReceived)
▼
[WEAR_PIPE] postToRxBus <-- отправка события в "шину" RxBus
v
[RxBus] ← «громкоговоритель» внутри часов
(в логах на циферблате: WEAR_FACE Rx SingleBg)
▼
[Watchface / CircleWatchface]
├─ invalidate() ← «перерисовать!»
└─ onDraw() ← «нарисовано»
(в логах: WEAR_FACE onDraw(); +Xms after invalidate())
▼
[ЭКРАН ЧАСОВ] ← видно новое BG + др. данные.
Какие файлы были переработаны
1. Циферблат
? wear/src/main/kotlin/app/aaps/wear/watchfaces/CircleWatchface.kt
-
Добавлены подробные логи (Log.d) в методах:
при получении событий от RxBus (Rx SingleBg, Rx Status),
при вызове invalidate(),
при отрисовке onDraw().
Добавлены счётчики времени (SystemClock.elapsedRealtime()), чтобы видеть задержку между событиями.
Упрощено отображение (SGV, Δ, ago, debug).
2. Сервис приёма данных
? wear/src/main/kotlin/app/aaps/wear/watchfaces/CircleWatchface.kt
-
Логирование добавлено в:
onMessageReceived() — чтобы зафиксировать момент, когда данные пришли от телефона.
перед rxBus.send() — чтобы отметить передачу события дальше в приложение.
Введён префикс WEAR_PIPE в логах, чтобы отделять уровень «канала данных» от уровня «отрисовки».
Усилен keepAlive/watchdog, чтобы контролировать связь (пинг каждые 30 секунд, проверка обрыва >120s).
Добавлен Partial wakeLog который будет экран когда приходят новые данные, иначе во сне они не перерисовываются (Partial WakeLock держит CPU бодрым, но экран не включает.)
Что показал анализ логов
Циферблат обновляется мгновенно (p50 ~5 мс, p90 ~10 мс).
Иногда есть редкие всплески (до секунд), но это единичные случаи.
Интервалы прихода SingleBg — в среднем ~54 секунды, максимальный разрыв в логе — ~4 минуты (меньше «критических» 7 минут).
Настоящая причина зависаний — не отрисовка, а то, что событие SingleBg не всегда сразу приходит от телефона на часы.
Итог
На всём пути разработки удалось пройти несколько этапов диагностики и улучшений:
-
Начало проблемы
На часах Galaxy Watch 5 Pro наблюдались зависания: данные сахара могли не обновляться до 30 минут.
Первые логи показали, что циферблат CircleWatchface отрисовывает данные мгновенно, но новые значения (SingleBg) не всегда приходят на часы.
Вывод: проблема не в логике циферблата, а в канале связи DataLayer ↔ телефон.
-
Разделение уровней логами
Добавили префиксы логов WEAR_PIPE (канал данных) и WEAR_FACE (отрисовка).
-
Это позволило точно понимать, где пропадают данные:
если нет WEAR_PIPE onMessageReceived → телефон не прислал данные, сбой DataLayer;
если Rx SingleBg дошёл → циферблат перерисует экран за миллисекунды.
-
Редкие провалы
В логах фиксировались разрывы по 3–5 минут.
Причина: телефон → часы иногда не отправляли пакет вовремя (особенность канала DataLayer).
Но после получения данных отрисовка всегда шла быстро (onDraw() < 10 мс).
-
Основная находка
Внимательное исследование показало, что при приглушённом или спящем экране сами часы не вызывают отрисовку.
В логах это выглядело как пачка onDraw() вызовов разом, когда пользователь «будил» экран.
Таким образом, задержка часто была не в DataLayer, а в поведении системы при ambient-режиме.
-
Принятые меры
В CircleWatchface внедрён wakeLock на входящих событиях — теперь процессор часов кратко «будится», даже если экран спит.
Добавлены логи с метками времени, чтобы видеть задержку между событием и invalidate/onDraw.
Усилен watchdog в DataLayerListenerServiceWear (ping каждые 30 сек, реакция на обрывы).
Финальный результат
Циферблат CircleWatchface работает корректно и отрисовывает данные мгновенно.
Редкие задержки изначально были вызваны каналом DataLayer ↔ телефон, но позже выяснилось, что большую роль играет сон/ambient часов, где система откладывает отрисовку.
Благодаря wakeLock и дополнительным логам данные теперь обновляются и отображаются на часах с минимальными задержками даже в спящем режиме.
Группа для обратной связи и опыта использования здесь https://t.me/+e1X1IQa9LFkzMmE6
Комментарии (0)
APSik Автор
15.09.2025 19:39в итоге залипания продолжились и пришлось немного доработать но сейчас все работает стабильно
сделал сервис публикации снапшотов (WearSnapshotService + WearSnapshotPublisher), добавили debug-ресивер ForceSnapshotReceiver, чтобы можно было руками пнуть и проверить отправку, подчистил манифесты (убрал дубли FileProvider, вынес debug-штуки в app/src/debug/), навел порядок с логами, чтобы было видно именно задержки/отправку.
Изменения уже в мастере, дальнейшие доработки будут в wear-dev
APSik Автор
Оказывается было еще несколько доработок которые пришлось сделать для обеспечения стабильной связи
Все они после финального теста будут в мастере