Это руководство совместимо с Health Connect версии 1.1.0-alpha11 .
Health Connect предоставляет тип данных о запланированных тренировках , позволяющий приложениям для тренировок составлять планы тренировок и читать их. Записанные упражнения (тренировки) можно считывать для персонализированного анализа эффективности, помогая пользователям достигать своих тренировочных целей.
Доступность функций
Чтобы определить, поддерживает ли устройство пользователя планы тренировок в Health Connect, проверьте наличиеFEATURE_PLANNED_EXERCISE
на клиенте:if (healthConnectClient
.features
.getFeatureStatus(
HealthConnectFeatures.FEATURE_PLANNED_EXERCISE
) == HealthConnectFeatures.FEATURE_STATUS_AVAILABLE) {
// Feature is available
} else {
// Feature isn't available
}
Более подробную информацию см. в разделе Проверка доступности функции .
Требуемые разрешения
Доступ к запланированным учениям защищен следующими разрешениями:
-
android.permission.health.READ_PLANNED_EXERCISE
-
android.permission.health.WRITE_PLANNED_EXERCISE
Чтобы добавить в приложение возможность плановых тренировок, начните с запроса разрешений на запись для типа данных PlannedExerciseSession
.
Вот разрешение, которое вам необходимо заявить, чтобы иметь возможность записать запланированное упражнение:
<application>
<uses-permission
android:name="android.permission.health.WRITE_PLANNED_EXERCISE" />
...
</application>
Чтобы прочитать запланированное упражнение, вам необходимо запросить следующие разрешения:
<application>
<uses-permission
android:name="android.permission.health.READ_PLANNED_EXERCISE" />
...
</application>
Запросить разрешения у пользователя
После создания клиентского экземпляра ваше приложение должно запрашивать разрешения у пользователя. Пользователи должны иметь возможность предоставлять или отклонять разрешения в любое время.
Для этого создайте набор разрешений для необходимых типов данных. Убедитесь, что разрешения в наборе предварительно объявлены в манифесте Android.
// Create a set of permissions for required data types
val PERMISSIONS =
setOf(
HealthPermission.getReadPermission(HeartRateRecord::class),
HealthPermission.getWritePermission(HeartRateRecord::class),
HealthPermission.getReadPermission(PlannedExerciseSessionRecord::class),
HealthPermission.getWritePermission(PlannedExerciseSessionRecord::class),
HealthPermission.getReadPermission(ExerciseSessionRecord::class),
HealthPermission.getWritePermission(ExerciseSessionRecord::class)
)
Используйте getGrantedPermissions
, чтобы проверить, предоставлены ли приложению необходимые разрешения. Если нет, запросите их с помощью createRequestPermissionResultContract
. Откроется экран разрешений Health Connect.
// Create the permissions launcher
val requestPermissionActivityContract = PermissionController.createRequestPermissionResultContract()
val requestPermissions = registerForActivityResult(requestPermissionActivityContract) { granted ->
if (granted.containsAll(PERMISSIONS)) {
// Permissions successfully granted
} else {
// Lack of required permissions
}
}
suspend fun checkPermissionsAndRun(healthConnectClient: HealthConnectClient) {
val granted = healthConnectClient.permissionController.getGrantedPermissions()
if (granted.containsAll(PERMISSIONS)) {
// Permissions already granted; proceed with inserting or reading data
} else {
requestPermissions.launch(PERMISSIONS)
}
}
Поскольку пользователи могут предоставлять или отзывать разрешения в любое время, ваше приложение должно периодически проверять наличие предоставленных разрешений и обрабатывать ситуации, когда разрешение теряется.
Связанные разрешения
Планы тренировок привязаны к сеансам тренировок . Поэтому для полноценного использования этой функции Health Connect пользователю необходимо дать разрешение на использование каждого типа записи, связанного с планом тренировок.
Например, если план тренировок измеряет частоту сердечных сокращений пользователя во время серии пробежек, разработчику может потребоваться объявить следующие разрешения и предоставить их пользователю, чтобы записать сеанс тренировки и прочитать результаты для последующей оценки:
-
android.permission.health.READ_EXERCISE
-
android.permission.health.READ_EXERCISE_ROUTES
-
android.permission.health.READ_HEART_RATE
-
android.permission.health.WRITE_EXERCISE
-
android.permission.health.WRITE_EXERCISE_ROUTE
-
android.permission.health.WRITE_HEART_RATE
Однако часто приложение, которое создаёт планы тренировок и сравнивает результаты с ними, отличается от приложения, которое использует планы тренировок и записывает данные о фактических упражнениях. В зависимости от типа приложения могут потребоваться не все разрешения на чтение и запись. Например, для каждого типа приложения могут потребоваться только следующие разрешения:
Приложение для планирования тренировок | Приложение для тренировок |
---|---|
WRITE_PLANNED_EXERCISE | READ_PLANNED_EXERCISE |
READ_EXERCISE | WRITE_EXERCISE |
READ_EXERCISE_ROUTES | WRITE_EXERCISE_ROUTE |
READ_HEART_RATE | WRITE_HEART_RATE |
Информация, включенная в запись запланированного сеанса тренировки
- Название сессии.
- Список запланированных блоков упражнений .
- Время начала и окончания сеанса.
- Тип упражнения.
- Примечания к занятию.
- Метаданные.
- Идентификатор завершенного сеанса упражнений — записывается автоматически после завершения сеанса упражнений, связанного с данным запланированным сеансом упражнений.
Информация, включенная в запись запланированного блока упражнений
Запланированный блок упражнений содержит список этапов упражнений для обеспечения повторения различных групп шагов (например, выполните последовательность сгибаний рук, отжиманий и скручиваний пять раз подряд).
- Описание блока.
- Список запланированных этапов упражнений .
- Количество повторений.
Информация, включенная в запись запланированных шагов упражнений
- Описание шага.
- Категория упражнений .
- Тип упражнения .
- Список целей по производительности .
- Цель завершения .
Поддерживаемые агрегации
Для этого типа данных не поддерживаются агрегации.
Пример использования
Предположим, пользователь планирует 90-минутную пробежку через два дня. Эта пробежка будет состоять из трёх кругов вокруг озера с целевой частотой пульса от 90 до 110 ударов в минуту.
- Пользователь в приложении плана тренировок определяет запланированную сессию тренировок со следующими параметрами:
- Планируемое начало и конец пробега
- Вид упражнения (бег)
- Количество кругов (повторений)
- Целевой показатель частоты сердечных сокращений (от 90 до 110 ударов в минуту)
- Эта информация группируется в блоки упражнений и шаги и записывается в Health Connect приложением плана тренировок как
PlannedExerciseSessionRecord
. - Пользователь выполняет запланированный сеанс (бег).
- Данные об упражнениях, относящиеся к сеансу, записываются:
- С помощью носимого устройства во время сеанса. Например, пульс. Эти данные записываются в Health Connect как тип записи для активности. В данном случае —
HeartRateRecord
. - Вручную пользователем после сеанса. Например, для указания времени начала и окончания пробежки. Эти данные записываются в Health Connect как
ExerciseSessionRecord
.
- С помощью носимого устройства во время сеанса. Например, пульс. Эти данные записываются в Health Connect как тип записи для активности. В данном случае —
- Позднее приложение плана тренировок считывает данные из Health Connect, чтобы оценить фактические результаты и сравнить их с целями, установленными пользователем в запланированном сеансе тренировок.
Планируйте упражнения и ставьте цели
Пользователь может планировать свои тренировки на будущее и ставить цели. Запишите это в Health Connect как запланированную тренировку .
В примере, описанном в разделе «Пример использования» , пользователь планирует 90-минутную пробежку через два дня. Эта пробежка будет состоять из трёх кругов вокруг озера с целевым пульсом от 90 до 110 ударов в минуту.
Подобный фрагмент кода можно найти в обработчике форм приложения, которое регистрирует запланированные тренировки в Health Connect. Его также можно найти в точке входа для интеграции, например, с сервисом, предлагающим тренировки.
// Verify the user has granted all necessary permissions for this task
val grantedPermissions =
healthConnectClient.permissionController.getGrantedPermissions()
if (!grantedPermissions.contains(
HealthPermission.getWritePermission(PlannedExerciseSessionRecord::class))) {
// The user hasn't granted the app permission to write planned exercise session data.
return
}
val plannedDuration = Duration.ofMinutes(90)
val plannedStartDate = LocalDate.now().plusDays(2)
val plannedExerciseSessionRecord = PlannedExerciseSessionRecord(
startDate = plannedStartDate,
duration = plannedDuration,
exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_RUNNING,
blocks = listOf(
PlannedExerciseBlock(
repetitions = 1, steps = listOf(
PlannedExerciseStep(
exerciseType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_RUNNING,
exercisePhase = PlannedExerciseStep.EXERCISE_PHASE_ACTIVE,
completionGoal = ExerciseCompletionGoal.RepetitionsGoal(repetitions = 3),
performanceTargets = listOf(
ExercisePerformanceTarget.HeartRateTarget(
minHeartRate = 90.0, maxHeartRate = 110.0
)
)
),
), description = "Three laps around the lake"
)
),
title = "Run at lake",
notes = null,
metadata = Metadata.manualEntry(
device = Device(type = Device.Companion.TYPE_PHONE)
)
)
val insertedPlannedExerciseSessions =
healthConnectClient.insertRecords(listOf(plannedExerciseSessionRecord)).recordIdsList
val insertedPlannedExerciseSessionId = insertedPlannedExerciseSessions.first()
Данные о тренировках и активности
Через два дня пользователь регистрирует сам сеанс тренировки. Запишите это в Health Connect как сеанс тренировки .
В этом примере продолжительность сеанса пользователя точно совпала с запланированной.
Следующий фрагмент кода можно найти в обработчике форм приложения, которое регистрирует сеансы тренировок в Health Connect. Его также можно найти в обработчиках сбора и экспорта данных для носимых устройств, способных обнаруживать и регистрировать сеансы тренировок.
В данном случае insertedPlannedExerciseSessionId
используется повторно из предыдущего примера. В реальном приложении идентификатор определялся бы пользователем, выбирающим запланированную тренировку из списка существующих.
// Verify the user has granted all necessary permissions for this task
val grantedPermissions =
healthConnectClient.permissionController.getGrantedPermissions()
if (!grantedPermissions.contains(
HealthPermission.getWritePermission(ExerciseSessionRecord::class))) {
// The user doesn't granted the app permission to write exercise session data.
return
}
val sessionDuration = Duration.ofMinutes(90)
val sessionEndTime = Instant.now()
val sessionStartTime = sessionEndTime.minus(sessionDuration)
val exerciseSessionRecord = ExerciseSessionRecord(
startTime = sessionStartTime,
startZoneOffset = ZoneOffset.UTC,
endTime = sessionEndTime,
endZoneOffset = ZoneOffset.UTC,
exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_RUNNING,
segments = listOf(
ExerciseSegment(
startTime = sessionStartTime,
endTime = sessionEndTime,
repetitions = 3,
segmentType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_RUNNING
)
),
title = "Run at lake",
plannedExerciseSessionId = insertedPlannedExerciseSessionId,
metadata = Metadata.manualEntry(
device = Device(type = Device.Companion.TYPE_PHONE)
)
)
val insertedExerciseSessions =
healthConnectClient.insertRecords(listOf(exerciseSessionRecord))
Носимое устройство также регистрирует пульс во время пробежки. Следующий фрагмент кода можно использовать для создания записей в пределах целевого диапазона.
В реальном приложении основные части этого фрагмента можно найти в обработчике сообщений от носимого устройства, который будет записывать измерения в Health Connect после их получения.
// Verify the user has granted all necessary permissions for this task
val grantedPermissions =
healthConnectClient.permissionController.getGrantedPermissions()
if (!grantedPermissions.contains(
HealthPermission.getWritePermission(HeartRateRecord::class))) {
// The user doesn't granted the app permission to write heart rate record data.
return
}
val samples = mutableListOf<HeartRateRecord.Sample>()
var currentTime = sessionStartTime
while (currentTime.isBefore(sessionEndTime)) {
val bpm = Random.nextInt(21) + 90
val heartRateRecord = HeartRateRecord.Sample(
time = currentTime,
beatsPerMinute = bpm.toLong(),
)
samples.add(heartRateRecord)
currentTime = currentTime.plusSeconds(180)
}
val heartRateRecord = HeartRateRecord(
startTime = sessionStartTime,
startZoneOffset = ZoneOffset.UTC,
endTime = sessionEndTime,
endZoneOffset = ZoneOffset.UTC,
samples = samples,
metadata = Metadata.autoRecorded(
device = Device(type = Device.Companion.TYPE_WATCH)
)
)
val insertedHeartRateRecords = healthConnectClient.insertRecords(listOf(heartRateRecord))
Оценить целевые показатели эффективности
На следующий день после тренировки пользователя вы можете получить записанные данные об тренировке, проверить наличие запланированных целей и оценить дополнительные типы данных, чтобы определить, были ли достигнуты поставленные цели.
Подобный фрагмент кода, скорее всего, можно найти в периодическом задании по оценке целевых показателей эффективности или при загрузке списка упражнений и отображении уведомления о целевых показателях эффективности в приложении.
// Verify the user has granted all necessary permissions for this task
val grantedPermissions =
healthConnectClient.permissionController.getGrantedPermissions()
if (!grantedPermissions.containsAll(
listOf(
HealthPermission.getReadPermission(ExerciseSessionRecord::class),
HealthPermission.getReadPermission(PlannedExerciseSessionRecord::class),
HealthPermission.getReadPermission(HeartRateRecord::class)
)
)
) {
// The user doesn't granted the app permission to read exercise session record data.
return
}
val searchDuration = Duration.ofDays(1)
val searchEndTime = Instant.now()
val searchStartTime = searchEndTime.minus(searchDuration)
val response = healthConnectClient.readRecords(
ReadRecordsRequest<ExerciseSessionRecord>(
timeRangeFilter = TimeRangeFilter.between(searchStartTime, searchEndTime)
)
)
for (exerciseRecord in response.records) {
val plannedExerciseRecordId = exerciseRecord.plannedExerciseSessionId
val plannedExerciseRecord =
if (plannedExerciseRecordId == null) null else healthConnectClient.readRecord(
PlannedExerciseSessionRecord::class, plannedExerciseRecordId
).record
if (plannedExerciseRecord != null) {
val aggregateRequest = AggregateRequest(
metrics = setOf(HeartRateRecord.BPM_AVG),
timeRangeFilter = TimeRangeFilter.between(
exerciseRecord.startTime, exerciseRecord.endTime
),
)
val aggregationResult = healthConnectClient.aggregate(aggregateRequest)
val maxBpm = aggregationResult[HeartRateRecord.BPM_MAX]
val minBpm = aggregationResult[HeartRateRecord.BPM_MIN]
if (maxBpm != null && minBpm != null) {
plannedExerciseRecord.blocks.forEach { block ->
block.steps.forEach { step ->
step.performanceTargets.forEach { target ->
when (target) {
is ExercisePerformanceTarget.HeartRateTarget -> {
val minTarget = target.minHeartRate
val maxTarget = target.maxHeartRate
if(
minBpm >= minTarget && maxBpm <= maxTarget
) {
// Success!
}
}
// Handle more target types
}
}
}
}
}
}
}
}
Сеансы упражнений
Тренировки могут включать в себя все, что угодно: от бега до бадминтона.
Запишите сеансы упражнений
Вот как создать запрос на вставку, включающий сеанс:
suspend fun writeExerciseSession(healthConnectClient: HealthConnectClient) {
healthConnectClient.insertRecords(
listOf(
ExerciseSessionRecord(
startTime = START_TIME,
startZoneOffset = START_ZONE_OFFSET,
endTime = END_TIME,
endZoneOffset = END_ZONE_OFFSET,
exerciseType = ExerciseSessionRecord.ExerciseType.RUNNING,
title = "My Run"
),
// ... other records
)
)
}
Прочитайте сеанс упражнений
Вот пример того, как читать сеанс упражнений:
suspend fun readExerciseSessions(
healthConnectClient: HealthConnectClient,
startTime: Instant,
endTime: Instant
) {
val response =
healthConnectClient.readRecords(
ReadRecordsRequest(
ExerciseSessionRecord::class,
timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
)
)
for (exerciseRecord in response.records) {
// Process each exercise record
// Optionally pull in with other data sources of the same time range.
val distanceRecord =
healthConnectClient
.readRecords(
ReadRecordsRequest(
DistanceRecord::class,
timeRangeFilter =
TimeRangeFilter.between(
exerciseRecord.startTime,
exerciseRecord.endTime
)
)
)
.records
}
}
Запись подтипа данных
Сеансы также могут состоять из необязательных подтипов данных, которые обогащают сеанс дополнительной информацией.
Например, сеансы упражнений могут включать классы ExerciseSegment
, ExerciseLap
и ExerciseRoute
:
val segments = listOf(
ExerciseSegment(
startTime = Instant.parse("2022-01-02T10:10:10Z"),
endTime = Instant.parse("2022-01-02T10:10:13Z"),
segmentType = ActivitySegmentType.BENCH_PRESS,
repetitions = 373
)
)
val laps = listOf(
ExerciseLap(
startTime = Instant.parse("2022-01-02T10:10:10Z"),
endTime = Instant.parse("2022-01-02T10:10:13Z"),
length = 0.meters
)
)
ExerciseSessionRecord(
exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_CALISTHENICS,
startTime = Instant.parse("2022-01-02T10:10:10Z"),
endTime = Instant.parse("2022-01-02T10:10:13Z"),
startZoneOffset = ZoneOffset.UTC,
endZoneOffset = ZoneOffset.UTC,
segments = segments,
laps = laps,
route = route
)