Сервис Health Services обеспечивает поддержку приложений для тренировок через ExerciseClient . С помощью ExerciseClient ваше приложение может контролировать ход выполнения упражнения, добавлять цели тренировки и получать обновления о состоянии упражнения, событиях тренировки или других показателях. Для получения дополнительной информации см. полный список типов упражнений , поддерживаемых сервисом Health Services.
Пример упражнения можно посмотреть на GitHub.
Добавить зависимости
Чтобы добавить зависимость от Health Services, необходимо добавить репозиторий Google Maven в ваш проект. Для получения дополнительной информации см. репозиторий Google Maven .
Затем в файле build.gradle на уровне модуля добавьте следующую зависимость:
классный
dependencies { implementation "androidx.health:health-services-client:1.1.0-alpha05" }
Котлин
dependencies { implementation("androidx.health:health-services-client:1.1.0-alpha05") }
Структура приложения
При создании приложения для тренировок с использованием сервисов здравоохранения используйте следующую структуру приложения:
- Размещайте все экраны и навигацию в рамках основного действия.
- Управляйте состоянием тренировки, данными датчиков, текущей активностью и другими данными с помощью фоновой службы .
- Сохраняйте данные в Room , а для загрузки используйте WorkManager .
При подготовке к тренировке и во время нее ваша активность может быть прервана по разным причинам. Пользователь может переключиться на другое приложение или вернуться к циферблату часов. Система может отображать что-то поверх вашей активности, или экран может выключиться после периода бездействия. Используйте постоянно работающий ForegroundService в сочетании с ExerciseClient , чтобы обеспечить корректную работу на протяжении всей тренировки.
Использование ForegroundService позволяет применять API текущей активности для отображения индикатора на поверхности ваших часов, что дает пользователю возможность быстро вернуться к тренировке.
Крайне важно правильно запрашивать данные о местоположении в вашей службе переднего плана. В файле манифеста укажите необходимые типы служб переднего плана и права доступа :
<manifest ...> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <application ...> <!-- If your app is designed only for devices that run Wear OS 4 or lower, use android:foregroundServiceType="location" instead. --> <service android:name=".MyExerciseSessionRecorder" android:foregroundServiceType="health|location"> </service> </application> </manifest>
Используйте AmbientLifecycleObserver для подготовки к тренировке (он содержит вызов prepareExercise() и для самой тренировки. Однако не обновляйте дисплей во время тренировки в режиме ожидания: это связано с тем, что Health Services объединяет данные о тренировке в пакеты, когда экран устройства находится в режиме ожидания, для экономии энергии, поэтому отображаемая информация может быть устаревшей. Во время тренировок отображайте данные, которые понятны пользователю, либо актуальную информацию, либо пустой экран.
Проверить возможности
Каждый ExerciseType поддерживает определенные типы данных для метрик и целей тренировок. Проверьте эти возможности при запуске, поскольку они могут различаться в зависимости от устройства. Устройство может не поддерживать определенный тип упражнений или определенную функцию, например, автоматическую паузу. Кроме того, возможности устройства могут меняться со временем, например, после обновления программного обеспечения.
При запуске приложения запросите информацию о возможностях устройства, сохраните и обработайте следующие данные:
- Упражнения, которые поддерживает платформа.
- Функциональные возможности, поддерживаемые для каждого упражнения.
- Типы данных, поддерживаемые для каждого упражнения.
- Для каждого из этих типов данных требуются соответствующие разрешения.
Используйте ExerciseCapabilities.getExerciseTypeCapabilities() с выбранным типом упражнений, чтобы узнать, какие метрики можно запросить, какие цели упражнений можно настроить и какие другие функции доступны для этого типа. Это показано в следующем примере:
val healthClient = HealthServices.getClient(this /*context*/)
val exerciseClient = healthClient.exerciseClient
lifecycleScope.launch {
val capabilities = exerciseClient.getCapabilitiesAsync().await()
if (ExerciseType.RUNNING in capabilities.supportedExerciseTypes) {
runningCapabilities =
capabilities.getExerciseTypeCapabilities(ExerciseType.RUNNING)
}
}
Внутри возвращаемого объекта ExerciseTypeCapabilities , supportedDataTypes перечисляет типы данных, для которых можно запрашивать данные. Этот список различается в зависимости от устройства, поэтому будьте внимательны и не запрашивайте DataType который не поддерживается, иначе ваш запрос может завершиться неудачей.
Используйте поля supportedGoals и supportedMilestones , чтобы определить, может ли данное упражнение способствовать достижению цели, которую вы хотите создать.
Если ваше приложение позволяет пользователю использовать автоматическую паузу, необходимо проверить поддержку этой функции устройством с помощью supportsAutoPauseAndResume . ExerciseClient отклоняет запросы, не поддерживаемые устройством.
В следующем примере проверяется поддержка типа данных HEART_RATE_BPM , возможности установки цели STEPS_TOTAL и функции автоматической паузы:
// Whether we can request heart rate metrics.
supportsHeartRate = DataType.HEART_RATE_BPM in runningCapabilities.supportedDataTypes
// Whether we can make a one-time goal for aggregate steps.
val stepGoals = runningCapabilities.supportedGoals[DataType.STEPS_TOTAL]
supportsStepGoals =
(stepGoals != null && ComparisonType.GREATER_THAN_OR_EQUAL in stepGoals)
// Whether auto-pause is supported.
val supportsAutoPause = runningCapabilities.supportsAutoPauseAndResume
Зарегистрируйтесь для получения обновлений о состоянии здоровья во время тренировок.
Обновления о тренировках доставляются слушателю. Ваше приложение может зарегистрировать только одного слушателя одновременно. Настройте слушателя перед началом тренировки, как показано в следующем примере. Ваш слушатель получает обновления только о тренировках, принадлежащих вашему приложению.
val callback = object : ExerciseUpdateCallback {
override fun onExerciseUpdateReceived(update: ExerciseUpdate) {
val exerciseStateInfo = update.exerciseStateInfo
val activeDuration = update.activeDurationCheckpoint
val latestMetrics = update.latestMetrics
val latestGoals = update.latestAchievedGoals
}
override fun onLapSummaryReceived(lapSummary: ExerciseLapSummary) {
// For ExerciseTypes that support laps, this is called when a lap is marked.
}
override fun onAvailabilityChanged(
dataType: DataType<*, *>,
availability: Availability
) {
// Called when the availability of a particular DataType changes.
when {
availability is LocationAvailability -> // Relates to Location/GPS.
availability is DataTypeAvailability -> // Relates to another DataType.
}
}
}
exerciseClient.setUpdateCallback(callback)
Управляйте своим временем занятий спортом.
Приложение Health Services поддерживает отслеживание не более одного упражнения одновременно во всех приложениях на устройстве. Если отслеживается одно упражнение, а другое приложение начинает отслеживать новое упражнение, отслеживание первого упражнения прекращается.
Перед началом тренировки выполните следующие действия:
- Проверьте, отслеживается ли уже данное упражнение, и отреагируйте соответствующим образом. Например, запросите у пользователя подтверждение, прежде чем отменить отслеживание предыдущего упражнения и начать отслеживать новое.
В следующем примере показано, как проверить наличие существующего упражнения с помощью getCurrentExerciseInfoAsync :
lifecycleScope.launch {
val exerciseInfo = exerciseClient.getCurrentExerciseInfoAsync().await()
when (exerciseInfo.exerciseTrackedStatus) {
OTHER_APP_IN_PROGRESS -> // Warn user before continuing, will stop the existing workout.
OWNED_EXERCISE_IN_PROGRESS -> // This app has an existing workout.
NO_EXERCISE_IN_PROGRESS -> // Start a fresh workout.
}
}
Разрешения
При использовании ExerciseClient убедитесь, что ваше приложение запрашивает и поддерживает необходимые разрешения . Если ваше приложение использует данные LOCATION , убедитесь, что оно также запрашивает и поддерживает соответствующие разрешения для этих данных.
Для всех типов данных перед вызовом функций prepareExercise() или startExercise() выполните следующие действия:
- Укажите соответствующие разрешения для запрашиваемых типов данных в файле
AndroidManifest.xml. - Убедитесь, что пользователь предоставил необходимые разрешения. Дополнительную информацию см. в разделе «Запрос разрешений приложения» . Служба здравоохранения отклонит запрос, если необходимые разрешения еще не предоставлены.
Для получения данных о местоположении выполните следующие дополнительные шаги:
- Убедитесь, что GPS включен на устройстве, используя
isProviderEnabled(LocationManager.GPS_PROVIDER). При необходимости предложите пользователю открыть настройки местоположения . - Убедитесь, что на протяжении всего выполнения упражнения поддерживается объект
ForegroundServiceс соответствующимforegroundServiceType.
Приготовьтесь к тренировке
Некоторым датчикам, таким как GPS или датчик сердечного ритма, может потребоваться некоторое время для прогрева, или пользователь может захотеть просмотреть свои данные перед началом тренировки. Дополнительный метод prepareExerciseAsync() позволяет этим датчикам прогреться и получить данные без запуска таймера тренировки. Время подготовки не влияет на значение activeDuration .
Перед вызовом функции prepareExerciseAsync() проверьте следующее:
Проверьте общеплатформенные настройки местоположения. Пользователь управляет этой настройкой в главном меню «Настройки»; она отличается от проверки разрешений на уровне приложения.
Если эта настройка отключена, сообщите пользователю, что доступ к местоположению запрещен, и предложите включить ее, если вашему приложению требуется доступ к местоположению.
Убедитесь, что ваше приложение имеет разрешения на использование датчиков тела (уровень API 35 или ниже) или датчика сердечного ритма (уровень API 36+), распознавание активности и точное определение местоположения во время выполнения. При отсутствии разрешений запросите у пользователя необходимые разрешения, предоставив соответствующий контекст. Если пользователь не предоставляет конкретное разрешение, удалите типы данных, связанные с этим разрешением, из вызова
prepareExerciseAsync(). Если разрешения на использование датчиков тела (датчик сердечного ритма на уровне API 36+) и определение местоположения не предоставлены, не вызывайтеprepareExerciseAsync(), поскольку вызов prepare предназначен специально для получения стабильного сигнала сердечного ритма или GPS-координат перед началом тренировки. Приложение по-прежнему может получать данные о пройденном расстоянии, темпе, скорости и другие показатели, для которых эти разрешения не требуются.
Выполните следующие действия, чтобы убедиться в успешности вызова функции prepareExerciseAsync() :
- Используйте
AmbientLifecycleObserverдля активности перед тренировкой, которая содержит вызов функции prepare. - Вызовите
prepareExerciseAsync()из вашей фоновой службы. Если она находится не в службе и привязана к жизненному циклу активности, то подготовка датчика может быть прервана без необходимости. - Вызовите
endExercise(), чтобы отключить датчики и снизить энергопотребление, если пользователь отклонится от предтренировочного режима.
В следующем примере показано, как вызвать функцию prepareExerciseAsync() :
val warmUpConfig = WarmUpConfig(
ExerciseType.RUNNING,
setOf(
DataType.HEART_RATE_BPM,
DataType.LOCATION
)
)
// Only necessary to call prepareExerciseAsync if body sensor (API level 35
// or lower), heart rate (API level 36+), or location permissions are given.
exerciseClient.prepareExerciseAsync(warmUpConfig).await()
// Data and availability updates are delivered to the registered listener.
Как только приложение перейдет в состояние PREPARING , обновления доступности датчиков передаются в ExerciseUpdateCallback через onAvailabilityChanged() . Эта информация затем может быть представлена пользователю, чтобы он мог решить, начинать ли тренировку.
Начните тренировку
Чтобы начать упражнение, создайте ExerciseConfig в котором укажите тип упражнения, типы данных, для которых вы хотите получать метрики, а также любые цели или этапы упражнения.
Цели тренировки состоят из DataType и условия. Цели тренировки — это разовые цели, которые срабатывают при выполнении условия, например, когда пользователь пробегает определенное расстояние. Также можно установить контрольную точку тренировки. Контрольные точки тренировки могут срабатывать несколько раз, например, каждый раз, когда пользователь пробегает определенное расстояние сверх установленной дистанции.
В следующем примере показано, как создать по одной цели каждого типа:
const val CALORIES_THRESHOLD = 250.0
const val DISTANCE_THRESHOLD = 1_000.0 // meters
suspend fun startExercise() {
// Types for which we want to receive metrics.
val dataTypes = setOf(
DataType.HEART_RATE_BPM,
DataType.CALORIES_TOTAL,
DataType.DISTANCE
)
// Create a one-time goal.
val calorieGoal = ExerciseGoal.createOneTimeGoal(
DataTypeCondition(
dataType = DataType.CALORIES_TOTAL,
threshold = CALORIES_THRESHOLD,
comparisonType = ComparisonType.GREATER_THAN_OR_EQUAL
)
)
// Create a milestone goal. To make a milestone for every kilometer, set the initial
// threshold to 1km and the period to 1km.
val distanceGoal = ExerciseGoal.createMilestone(
condition = DataTypeCondition(
dataType = DataType.DISTANCE_TOTAL,
threshold = DISTANCE_THRESHOLD,
comparisonType = ComparisonType.GREATER_THAN_OR_EQUAL
),
period = DISTANCE_THRESHOLD
)
val config = ExerciseConfig(
exerciseType = ExerciseType.RUNNING,
dataTypes = dataTypes,
isAutoPauseAndResumeEnabled = false,
isGpsEnabled = true,
exerciseGoals = mutableListOf<ExerciseGoal<Double>>(calorieGoal, distanceGoal)
)
exerciseClient.startExerciseAsync(config).await()
}
Вы также можете отмечать круги для всех упражнений. Служба здравоохранения предоставляет сводку ExerciseLapSummary с показателями, агрегированными за период выполнения круга.
В предыдущем примере показано использование параметра isGpsEnabled , который должен быть установлен в значение true при запросе данных о местоположении. Однако использование GPS также может помочь с другими показателями. Если в ExerciseConfig в качестве DataType указано расстояние, по умолчанию для оценки расстояния используются шаги. При желании, включив GPS, можно использовать информацию о местоположении для оценки расстояния.
Приостановить, возобновить и завершить тренировку
Вы можете приостанавливать, возобновлять и завершать тренировки, используя соответствующий метод, например pauseExerciseAsync() или endExerciseAsync() .
Используйте состояние из ExerciseUpdate в качестве источника достоверной информации. Тренировка считается приостановленной не в момент возврата вызова pauseExerciseAsync() , а когда это состояние отражается в сообщении ExerciseUpdate . Это особенно важно учитывать при работе с состояниями пользовательского интерфейса. Если пользователь нажимает кнопку паузы, отключите кнопку паузы и вызовите pauseExerciseAsync() для Health Services. Дождитесь, пока Health Services перейдет в состояние паузы, используя ExerciseUpdate.exerciseStateInfo.state , а затем переключите кнопку на возобновление тренировки. Это связано с тем, что обновления состояния Health Services могут занимать больше времени, чем нажатие кнопки, поэтому, если вы привяжете все изменения пользовательского интерфейса к нажатиям кнопок, интерфейс может рассинхронизироваться с состоянием Health Services.
Учитывайте это в следующих ситуациях:
- Функция автоматической паузы включена: тренировку можно приостановить или возобновить без участия пользователя.
- Другое приложение начинает тренировку: ваша тренировка может быть прервана без участия пользователя.
Если тренировка в вашем приложении прерывается другим приложением, ваше приложение должно корректно обработать это прерывание:
- Сохраняйте состояние частичной тренировки, чтобы прогресс пользователя не был стерт.
- Удалите значок «Текущая активность» и отправьте пользователю уведомление о том, что его тренировка была завершена другим приложением.
Также обработайте случай отзыва разрешений во время выполнения упражнения. Это отправляется с помощью состояния isEnded с причиной ExerciseEndReason , равной AUTO_END_PERMISSION_LOST . Обработайте этот случай аналогично случаю завершения: сохраните частичное состояние, удалите значок «Текущая активность» и отправьте пользователю уведомление о произошедшем.
Следующий пример показывает, как правильно проверить наличие завершения процесса:
val callback = object : ExerciseUpdateCallback {
override fun onExerciseUpdateReceived(update: ExerciseUpdate) {
if (update.exerciseStateInfo.state.isEnded) {
// Workout has either been ended by the user, or otherwise terminated
}
...
}
...
}
Управление продолжительностью активности
Во время тренировки приложение может отображать активную продолжительность занятия. Приложение, сервис «Здоровье» и микроконтроллер устройства (MCU) — маломощный процессор, отвечающий за отслеживание тренировки — должны быть синхронизированы, имея одинаковую текущую активную продолжительность. Для этого сервис «Здоровье» отправляет контрольную точку ActiveDurationCheckpoint , которая служит отправной точкой для запуска таймера приложением.
Поскольку активная длительность передается с микроконтроллера и может потребоваться некоторое время, чтобы она дошла до приложения, ActiveDurationCheckpoint содержит два свойства:
-
activeDuration: продолжительность выполнения упражнения. -
time: когда рассчитывалась продолжительность активности
Таким образом, в приложении активную продолжительность упражнения можно рассчитать по ActiveDurationCheckpoint , используя следующее уравнение:
(now() - checkpoint.time) + checkpoint.activeDuration
Это объясняет небольшую разницу между временем выполнения, рассчитываемым на микроконтроллере, и временем, поступающим в приложение. Это можно использовать для инициализации хронометра в приложении и обеспечения идеального согласования таймера приложения со временем в системе здравоохранения и на микроконтроллере.
Если упражнение приостановлено, приложение ожидает перезапуска таймера в пользовательском интерфейсе до тех пор, пока вычисленное время не превысит отображаемое на экране значение. Это происходит потому, что сигнал паузы поступает в службу поддержки Health Services и на микроконтроллер с небольшой задержкой. Например, если приложение приостановлено на t=10 секундах, служба поддержки Health Services может не отправить приложению обновление PAUSED до t=10,2 секунд.
Работа с данными из ExerciseClient
Метрики для типов данных, зарегистрированных вашим приложением, передаются в сообщениях ExerciseUpdate .
Процессор отправляет сообщения только тогда, когда активен или когда достигнут максимальный период отчетности, например, каждые 150 секунд. Не полагайтесь на частоту ExerciseUpdate для продвижения хронометра с activeDuration . Пример реализации независимого хронометра можно найти в примере Exercise на GitHub.
Когда пользователь начинает тренировку, сообщения ExerciseUpdate могут отправляться часто, например, каждую секунду. В момент начала тренировки экран может выключиться. В этом случае Health Services может отправлять данные реже, но с той же частотой, чтобы избежать пробуждения основного процессора. Когда пользователь смотрит на экран, любые данные, находящиеся в процессе пакетной обработки, немедленно передаются в ваше приложение.
Контролируйте скорость пакетной обработки.
В некоторых сценариях может потребоваться контролировать частоту получения приложением определенных типов данных при выключенном экране. Объект BatchingMode позволяет приложению переопределить поведение пакетной обработки по умолчанию, чтобы получать данные чаще.
Для настройки скорости пакетной обработки выполните следующие шаги:
Проверьте, поддерживается ли устройство данным определением
BatchingMode:// Confirm BatchingMode support to control heart rate stream to phone. suspend fun supportsHrWorkoutCompanionMode(): Boolean { val capabilities = exerciseClient.getCapabilities() return BatchingMode.HEART_RATE_5_SECONDS in capabilities.supportedBatchingModeOverrides }Укажите, что объект
ExerciseConfigдолжен использовать определенныйBatchingMode, как показано в следующем фрагменте кода.val config = ExerciseConfig( exerciseType = ExerciseType.WORKOUT, dataTypes = setOf( DataType.HEART_RATE_BPM, DataType.TOTAL_CALORIES ), // ... batchingModeOverrides = setOf(BatchingMode.HEART_RATE_5_SECONDS) )При желании вы можете динамически настраивать режим
BatchingModeво время тренировки, вместо того чтобы устанавливать определенное поведение пакетной обработки на протяжении всей тренировки:val desiredModes = setOf(BatchingMode.HEART_RATE_5_SECONDS) exerciseClient.overrideBatchingModesForActiveExercise(desiredModes)Чтобы очистить настроенный
BatchingModeи вернуться к поведению по умолчанию, передайте пустой набор вexerciseClient.overrideBatchingModesForActiveExercise().
Временные метки
Каждая точка данных в определенный момент времени представляет собой период времени с момента загрузки устройства. Чтобы преобразовать это в метку времени, выполните следующие действия:
val bootInstant =
Instant.ofEpochMilli(System.currentTimeMillis() - SystemClock.elapsedRealtime())
Затем это значение можно использовать с getStartInstant() или getEndInstant() для каждой точки данных.
Точность данных
Для некоторых типов данных к каждой точке данных может быть привязана информация о точности. Это отображается в свойстве accuracy .
Для типов данных HEART_RATE_BPM и LOCATION можно заполнить значения в классах HrAccuracy и LocationAccuracy соответственно. Если свойство accuracy присутствует, используйте его, чтобы определить, достаточно ли точна каждая точка данных для вашего приложения.
Хранение и загрузка данных
Используйте Room для сохранения данных, полученных от медицинских служб. Загрузка данных происходит в конце выполнения задания с помощью такого механизма, как Work Manager . Это помогает убедиться, что сетевые запросы на загрузку данных откладываются до окончания задания, что минимизирует энергопотребление во время выполнения задания и упрощает работу.
Контрольный список интеграции
Перед публикацией приложения, использующего ExerciseClient от Health Services, ознакомьтесь со следующим контрольным списком, чтобы убедиться, что пользовательский интерфейс вашего приложения не содержит распространенных проблем. Убедитесь, что:
- Ваше приложение проверяет возможности типа упражнения и возможности устройства при каждом запуске. Таким образом, вы можете определить, когда конкретное устройство или упражнение не поддерживает один из типов данных, необходимых вашему приложению.
- Вы запрашиваете и поддерживаете необходимые разрешения и указываете их в файле манифеста. Перед вызовом
prepareExerciseAsync()ваше приложение подтверждает предоставление разрешений во время выполнения. - Ваше приложение использует
getCurrentExerciseInfoAsync()для обработки случаев, когда :- Упражнение уже отслеживается, и ваше приложение перезаписывает данные предыдущего упражнения.
- Ваше упражнение было прервано другим приложением. Это может произойти, когда пользователь снова откроет приложение: в этом случае он увидит сообщение о том, что упражнение было остановлено, потому что его перехватило другое приложение.
- Если вы используете данные
LOCATION:- Ваше приложение поддерживает объект
ForegroundServiceс соответствующимforegroundServiceTypeна протяжении всего выполнения упражнения (включая вызов prepare). - Убедитесь, что GPS включен на устройстве, используя
isProviderEnabled(LocationManager.GPS_PROVIDER), и при необходимости предложите пользователю открыть настройки местоположения. - Для ресурсоемких сценариев использования, где получение данных о местоположении с низкой задержкой имеет большое значение, рассмотрите возможность интеграции поставщика объединенных данных о местоположении (Fused Location Provider , FLP) и использования его данных в качестве первоначального определения местоположения. Если более стабильная информация о местоположении доступна от медицинских служб, используйте ее вместо FLP.
- Ваше приложение поддерживает объект
- Если вашему приложению требуется загрузка данных, любые сетевые запросы для загрузки данных откладываются до завершения упражнения. В противном случае, на протяжении всего упражнения ваше приложение будет совершать необходимые сетевые запросы крайне редко.
Рекомендуем вам
- Примечание: текст ссылки отображается, когда JavaScript отключен.
- Пассивные обновления данных
- Медицинские сервисы на Wear OS
- Начните с плитки