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") }
Структура приложения
При создании приложения для тренировок с использованием Health Services используйте следующую структуру приложения:
- Расположите экраны и навигацию в рамках основной деятельности.
- Управляйте состоянием тренировки, данными датчиков, текущей активностью и данными с помощью приоритетной службы .
- Храните данные в 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()
, и для вашей активности тренировки. Однако не обновляйте дисплей во время тренировки в режиме окружения: это связано с тем, что службы здравоохранения собирают данные о тренировке в пакеты, когда экран устройства находится в режиме окружения для экономии энергии, поэтому отображаемая информация может быть устаревшей. Во время тренировок показывайте данные, которые имеют смысл для пользователя, отображая либо актуальную информацию, либо пустой экран.
Проверить возможности
Каждый 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()
, так как вызов preparation предназначен специально для получения стабильной частоты сердечных сокращений или определения местоположения по GPS перед началом упражнения. Приложение по-прежнему может получать данные о расстоянии, темпе, скорости и других показателях на основе шагов, для которых не требуются эти разрешения.
Чтобы убедиться, что вызов prepareExerciseAsync()
будет выполнен успешно, выполните следующие действия:
- Используйте
AmbientLifecycleObserver
для предтренировочной активности, содержащей вызов подготовки. - Вызовите
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()
}
Вы также можете отмечать круги для всех упражнений. Health Services предоставляет ExerciseLapSummary
с метриками, агрегированными за период круга.
В предыдущем примере показано использование isGpsEnabled
, которое должно быть true при запросе данных о местоположении. Однако использование GPS также может помочь с другими метриками. Если ExerciseConfig
указывает расстояние как DataType
, по умолчанию используется количество шагов для оценки расстояния. При опциональном включении GPS вместо этого можно использовать информацию о местоположении для оценки расстояния.
Пауза, возобновление и завершение тренировки
Вы можете приостанавливать, возобновлять и завершать тренировки, используя соответствующий метод, например pauseExerciseAsync()
или endExerciseAsync()
.
Используйте состояние из ExerciseUpdate
в качестве источника истины. Тренировка не считается приостановленной, когда возвращается вызов pauseExerciseAsync()
, а вместо этого, когда это состояние отражается в сообщении ExerciseUpdate
. Это особенно важно учитывать, когда речь идет о состояниях пользовательского интерфейса. Если пользователь нажимает паузу, отключите кнопку паузы и вызовите pauseExerciseAsync()
в службах здравоохранения. Подождите, пока службы здравоохранения перейдут в состояние паузы с помощью ExerciseUpdate.exerciseStateInfo.state
, а затем переключите кнопку для возобновления. Это связано с тем, что обновления состояния служб здравоохранения могут занимать больше времени, чем нажатие кнопки, поэтому, если вы привяжете все изменения пользовательского интерфейса к нажатиям кнопок, пользовательский интерфейс может рассинхронизироваться с состоянием служб здравоохранения.
Помните об этом в следующих ситуациях:
- Включена функция автоматической паузы: тренировка может быть приостановлена или начата без участия пользователя.
- Другое приложение запускает тренировку: ваша тренировка может быть прервана без вмешательства пользователя.
Если тренировка вашего приложения завершена другим приложением, ваше приложение должно корректно обработать завершение:
- Сохраните состояние частичной тренировки, чтобы не стереть прогресс пользователя.
- Удалите значок текущей активности и отправьте пользователю уведомление о том, что его тренировка была завершена другим приложением.
Также обработайте случай, когда разрешения отзываются во время текущего упражнения. Это отправляется с использованием состояния isEnded
с ExerciseEndReason
AUTO_END_PERMISSION_LOST
. Обработайте этот случай аналогично случаю завершения: сохраните частичное состояние, удалите значок Ongoing Activity и отправьте уведомление о том, что произошло, пользователю.
В следующем примере показано, как правильно проверить завершение:
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
}
...
}
...
}
Управление продолжительностью активности
Во время упражнения приложение может отображать активную продолжительность тренировки. Приложение, Health Services и микроконтроллер (MCU) устройства — маломощный процессор, отвечающий за отслеживание упражнений — должны быть синхронизированы с одинаковой текущей активной продолжительностью. Чтобы помочь управлять этим, Health Services отправляет ActiveDurationCheckpoint
, который предоставляет точку привязки, с которой приложение может запустить свой таймер.
Поскольку активная длительность отправляется из MCU и может занять некоторое время, чтобы попасть в приложение, ActiveDurationCheckpoint
содержит два свойства:
-
activeDuration
: как долго упражнение было активным -
time
: когда была рассчитана активная продолжительность
Таким образом, в приложении активную продолжительность упражнения можно рассчитать из ActiveDurationCheckpoint
, используя следующее уравнение:
(сейчас() - контрольная точка.время) + контрольная точка.активнаяДлительность
Это объясняет небольшую разницу между активной продолжительностью, вычисляемой на MCU и поступающей в приложение. Это можно использовать для заполнения хронометра в приложении и обеспечения идеального соответствия таймера приложения времени в Health Services и MCU.
Если упражнение приостановлено, приложение ждет, чтобы перезапустить таймер в пользовательском интерфейсе, пока расчетное время не превысит то, что в данный момент отображается в пользовательском интерфейсе. Это происходит потому, что сигнал паузы достигает служб здравоохранения и MCU с небольшой задержкой. Например, если приложение приостановлено в момент времени t=10 секунд, службы здравоохранения могут не доставить обновление PAUSED
приложению до момента времени t=10,2 секунды.
Работа с данными из ExerciseClient
Метрики для типов данных, для которых зарегистрировано ваше приложение, передаются в сообщениях ExerciseUpdate
.
Процессор доставляет сообщения только в состоянии бодрствования или при достижении максимального периода отчетности, например каждые 150 секунд. Не полагайтесь на частоту ExerciseUpdate
для продвижения хронометра с activeDuration
. См. пример Exercise на GitHub для примера того, как реализовать независимый хронометр.
Когда пользователь начинает тренировку, сообщения ExerciseUpdate
могут отправляться часто, например каждую секунду. Когда пользователь начинает тренировку, экран может выключиться. Службы здравоохранения могут затем отправлять данные реже, но по-прежнему с той же частотой, чтобы не разбудить основной процессор. Когда пользователь смотрит на экран, любые данные, находящиеся в процессе пакетирования, немедленно отправляются в ваше приложение.
Контролируйте скорость дозирования
В некоторых сценариях вам может потребоваться контролировать частоту, с которой ваше приложение получает определенные типы данных, пока экран выключен. Объект 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
.
Классы HrAccuracy
и LocationAccuracy
могут быть заполнены для типов данных HEART_RATE_BPM
и LOCATION
соответственно. Если присутствует, используйте свойство accuracy
, чтобы определить, достаточно ли точны все точки данных для вашего приложения.
Хранение и загрузка данных
Используйте Room для сохранения данных, полученных от Health Services. Загрузка данных происходит в конце упражнения с использованием механизма, подобного Work Manager . Это гарантирует, что сетевые вызовы для загрузки данных будут отложены до завершения упражнения, что минимизирует потребление энергии во время упражнения и упрощает работу.
Контрольный список интеграции
Перед публикацией приложения, использующего Health Services' ExerciseClient
, ознакомьтесь со следующим контрольным списком, чтобы убедиться, что ваш пользовательский опыт позволяет избежать некоторых распространенных проблем. Убедитесь, что:
- Ваше приложение проверяет возможности типа упражнения и возможности устройства каждый раз при запуске приложения. Таким образом, вы можете определить, когда конкретное устройство или упражнение не поддерживает один из типов данных, необходимых вашему приложению.
- Вы запрашиваете и поддерживаете необходимые разрешения и указываете их в вашем файле манифеста. Перед вызовом
prepareExerciseAsync()
ваше приложение подтверждает, что разрешения времени выполнения предоставлены. - Ваше приложение использует
getCurrentExerciseInfoAsync()
для обработки случаев, когда :- Упражнение уже отслеживается, и ваше приложение перезаписывает предыдущее упражнение.
- Другое приложение завершило ваше упражнение. Это может произойти, когда пользователь снова открывает приложение, он видит сообщение, объясняющее, что упражнение остановлено, поскольку другое приложение взяло управление на себя.
- Если вы используете данные
LOCATION
:- Ваше приложение поддерживает
ForegroundService
с соответствующимforegroundServiceType
на протяжении всего упражнения (включая вызов подготовки). - Проверьте, включен ли GPS на устройстве, с помощью
isProviderEnabled(LocationManager.GPS_PROVIDER)
и при необходимости предложите пользователю открыть настройки местоположения. - Для требовательных случаев использования, где получение данных о местоположении с низкой задержкой имеет большое значение, рассмотрите возможность интеграции Fused Location Provider (FLP) и использования его данных в качестве первоначального определения местоположения. Когда более стабильная информация о местоположении доступна от Health Services, используйте ее вместо FLP.
- Ваше приложение поддерживает
- Если ваше приложение требует загрузки данных, любые сетевые вызовы для загрузки данных откладываются до завершения упражнения. В противном случае, в течение упражнения ваше приложение делает все необходимые сетевые вызовы экономно.
Рекомендовано для вас
- Примечание: текст ссылки отображается, когда JavaScript отключен.
- Пассивные обновления данных
- Медицинские услуги на Wear OS
- Начните работу с плиткой