训练计划

本指南适用于 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

请在 Play 管理中心内为您的应用声明以下权限,并在应用的清单中声明这些权限:

<application>
  <uses-permission
android:name="android.permission.health.READ_PLANNED_EXERCISE" />
  <uses-permission
android:name="android.permission.health.WRITE_PLANNED_EXERCISE" />
...
</application>

您有责任声明您打算在设备和应用中使用的所有适当权限。您还应在使用前检查用户是否已授予每项权限。

锻炼计划与锻炼时段相关联。因此,用户必须授予使用与训练计划相关的每种记录类型的权限,才能充分利用 Health Connect 的此功能。

例如,如果训练计划在一系列跑步期间测量用户的心率,则开发者可能需要声明并由用户授予以下权限,才能写入锻炼时段并读取结果以供日后评估:

  • android.permission.health.READ_EXERCISE
  • android.permission.health.READ_EXERCISE_ROUTE
  • 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_ROUTE WRITE_EXERCISE_ROUTE
READ_HEART_RATE WRITE_HEART_RATE

计划锻炼时段记录中包含的信息

  • 会话的标题。
  • 计划的锻炼块列表。
  • 会话的开始时间和结束时间。
  • 锻炼类型。
  • 活动的备注。
  • 元数据。
  • 已完成的锻炼时段 ID - 在与此计划锻炼时段相关的锻炼时段完成后,系统会自动写入此 ID。

计划的锻炼分块记录中包含的信息

计划的锻炼分块包含一系列锻炼步骤,以支持重复不同的步骤组(例如,连续做五次手臂弯曲、burpee 和卷腹)。

计划锻炼步数记录中包含的信息

支持的汇总

此数据类型不支持任何汇总。

用法示例

假设用户计划在两天后跑步 90 分钟。此跑步活动将围绕湖泊跑三圈,目标心率为 90 到 110 bpm。

  1. 用户在训练计划应用中定义了以下内容,以便进行计划的锻炼活动:
    1. 计划的运行开始时间和结束时间
    2. 锻炼类型(跑步)
    3. 圈数(重复次数)
    4. 心率的效果目标(介于 90 到 110 bpm 之间)
  2. 这些信息会分组为锻炼分块和步数,并由训练计划应用以 PlannedExerciseSessionRecord 的形式写入 Health Connect。
  3. 用户执行计划的会话(正在运行)。
  4. 系统会在以下任一情况下记录与时段相关的锻炼数据:
    1. 在会议期间通过穿戴式设备。例如,心率。 系统会将此数据作为活动的记录类型写入 Health Connect。在此示例中为 HeartRateRecord
    2. 由用户在会话结束后手动完成。例如,指明实际运行的开始和结束时间。这些数据会作为 ExerciseSessionRecord 写入 Health Connect。
  5. 稍后,训练计划应用会从 Health Connect 读取数据,以便根据用户在计划的锻炼时段中设定的目标评估实际表现。

规划锻炼和设置目标

用户可以规划未来的锻炼活动并设置目标。将其作为计划的锻炼时段写入 Health Connect。

使用示例中所述的示例中,用户计划在两天后跑步 90 分钟。此跑步活动将围绕湖边跑三圈,目标心率为 90 到 110 bpm。

在将计划的锻炼时段记录到 Health Connect 的应用的表单处理程序中,可能会找到类似的代码段。您也可以在集成的最佳位置找到它,例如在提供培训的服务中。

// Ensure 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 = ExerciseSessionRecord.EXERCISE_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
)
val insertedPlannedExerciseSessions =
    healthConnectClient.insertRecords(listOf(plannedExerciseSessionRecord)).recordIdsList
val insertedPlannedExerciseSessionId = insertedPlannedExerciseSessions.first()

记录锻炼和活动数据

两天后,用户记录了实际的锻炼时段。将其作为锻炼时段写入 Health Connect。

在此示例中,用户的会话时长与计划时长完全一致。

在将锻炼时段记录到 Health Connect 的应用的表单处理程序中,您可能会看到以下代码段。它可能还会出现在能够检测和记录锻炼时段的穿戴式设备的数据提取和导出处理程序中。

此处的 insertedPlannedExerciseSessionId 是从前面的示例中重复使用的。在真实应用中,该 ID 由用户从现有锻炼时段列表中选择的计划锻炼时段决定。

// Ensure 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,
)
val insertedExerciseSessions =
    healthConnectClient.insertRecords(listOf(exerciseSessionRecord))

穿戴式设备还会记录用户在整个跑步过程中的心率。以下代码段可用于生成目标范围内的记录。

在真实应用中,此代码段的主要部分可能位于来自穿戴式设备的消息的处理程序中,该处理程序会在收集时将测量结果写入 Health Connect。

// Ensure 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,
)
val insertedHeartRateRecords = healthConnectClient.insertRecords(listOf(heartRateRecord))

评估效果目标

在用户锻炼后的第二天,您可以检索已记录的锻炼、检查是否有任何计划的锻炼目标,并评估其他数据类型,以确定是否达到了设定的目标。

在评估效果目标的定期作业中,或者在加载运动列表并在应用中显示有关效果目标的通知时,可能会出现这样的代码段。

// Ensure 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
                            }
                        }
                    }
                }
            }
        }
    }
}