Используемые устройства — телефон 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
ссылка на оригинал статьи https://habr.com/ru/articles/946832/
Добавить комментарий