Kế hoạch luyện tập

Hướng dẫn này tương thích với Health Connect phiên bản 1.1.0-alpha11.

Health Connect cung cấp một loại dữ liệu bài tập có kế hoạch để cho phép các ứng dụng huấn luyện ghi kế hoạch huấn luyện và cho phép các ứng dụng tập luyện đọc kế hoạch huấn luyện. Bạn có thể đọc lại các bài tập đã ghi (bài tập) để phân tích hiệu suất theo cách riêng nhằm giúp người dùng đạt được mục tiêu huấn luyện.

Phạm vi cung cấp tính năng

Để xác định xem thiết bị của người dùng có hỗ trợ kế hoạch tập luyện trên Health Connect hay không, hãy kiểm tra trạng thái cung cấp của FEATURE_PLANNED_EXERCISE trên ứng dụng:

if (healthConnectClient
     .features
     .getFeatureStatus(
       HealthConnectFeatures.FEATURE_PLANNED_EXERCISE
     ) == HealthConnectFeatures.FEATURE_STATUS_AVAILABLE) {

  // Feature is available
} else {
  // Feature isn't available
}

Hãy xem phần Kiểm tra phạm vi cung cấp của các tính năng để tìm hiểu thêm.

Các quyền bắt buộc

Quyền truy cập vào bài tập đã lên kế hoạch được bảo vệ bằng các quyền sau:

  • android.permission.health.READ_PLANNED_EXERCISE
  • android.permission.health.WRITE_PLANNED_EXERCISE

Để thêm chức năng bài tập có kế hoạch vào ứng dụng của bạn, hãy bắt đầu bằng cách yêu cầu quyền ghi cho kiểu dữ liệu PlannedExerciseSession.

Dưới đây là quyền bạn cần khai báo để có thể ghi bài tập đã lên kế hoạch:

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

Để đọc dữ liệu bài tập đã lên kế hoạch, bạn cần yêu cầu các quyền sau:

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

Yêu cầu người dùng cấp quyền

Sau khi tạo một phiên bản ứng dụng, ứng dụng của bạn cần yêu cầu người dùng cấp quyền. Người dùng phải được phép cấp hoặc từ chối cấp quyền bất cứ lúc nào.

Để thực hiện việc này, hãy tạo một tập hợp quyền cho các kiểu dữ liệu bắt buộc. Trước tiên, bạn cần khai báo các quyền trong tập hợp này ở tệp kê khai 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)
)

Hãy sử dụng getGrantedPermissions để xem ứng dụng của bạn đã được cấp các quyền cần thiết chưa. Nếu chưa, hãy sử dụng createRequestPermissionResultContract để yêu cầu các quyền đó. Thao tác này sẽ hiện màn hình các quyền của 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)
  }
}

Vì người dùng có thể cấp hoặc thu hồi quyền bất cứ lúc nào, nên ứng dụng của bạn cần kiểm tra định kỳ các quyền đã cấp và xử lý các tình huống khi mất quyền.

Kế hoạch luyện tập được liên kết với các phiên tập thể dục. Do đó, người dùng phải cấp quyền sử dụng từng loại bản ghi liên quan đến kế hoạch tập luyện để khai thác tối đa tính năng này của Health Connect.

Ví dụ: nếu một kế hoạch tập luyện đo nhịp tim của người dùng trong một loạt lượt chạy, thì nhà phát triển có thể cần khai báo và người dùng có thể cần cấp các quyền sau để ghi phiên tập thể dục và đọc kết quả để đánh giá sau này:

  • 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

Tuy nhiên, thường thì ứng dụng tạo kế hoạch tập luyện và đánh giá hiệu suất dựa trên kế hoạch không giống với ứng dụng sử dụng kế hoạch tập luyện và ghi dữ liệu tập luyện thực tế. Tuỳ thuộc vào loại ứng dụng, bạn không cần đến tất cả các quyền đọc và ghi. Ví dụ: bạn có thể chỉ cần những quyền này cho từng loại ứng dụng:

Ứng dụng kế hoạch luyện tập Ứng dụng Bài tập
WRITE_PLANNED_EXERCISE READ_PLANNED_EXERCISE
READ_EXERCISE WRITE_EXERCISE
READ_EXERCISE_ROUTES WRITE_EXERCISE_ROUTE
READ_HEART_RATE WRITE_HEART_RATE

Thông tin có trong bản ghi của một phiên tập thể dục đã lên kế hoạch

  • Tiêu đề của phiên.
  • Danh sách các khối bài tập đã lên kế hoạch.
  • Thời gian bắt đầu và kết thúc phiên.
  • Loại bài tập.
  • Ghi chú cho hoạt động.
  • Siêu dữ liệu.
  • Mã phiên tập luyện đã hoàn tất – Mã này được ghi tự động sau khi một phiên tập luyện liên quan đến phiên tập luyện đã lên kế hoạch này hoàn tất.

Thông tin có trong bản ghi khối bài tập đã lên kế hoạch

Một khối bài tập đã lên kế hoạch chứa danh sách các bước tập thể dục, để hỗ trợ việc lặp lại các nhóm bước khác nhau (ví dụ: thực hiện một chuỗi các động tác gập tay, động tác bật nhảy và động tác gập bụng 5 lần liên tiếp).

Thông tin có trong bản ghi bước tập thể dục đã lên kế hoạch

Các phép tổng hợp được hỗ trợ

Không có phép tổng hợp nào được hỗ trợ cho loại dữ liệu này.

Ví dụ về cách sử dụng

Giả sử một người dùng lên kế hoạch chạy bộ 90 phút trong 2 ngày nữa. Buổi chạy này sẽ có 3 vòng quanh hồ với tần số tim mục tiêu từ 90 đến 110 nhịp/phút.

  1. Người dùng xác định một phiên tập thể dục theo kế hoạch với những thông tin sau trong ứng dụng kế hoạch tập luyện:
    1. Thời gian bắt đầu và kết thúc dự kiến của lần chạy
    2. Loại bài tập (chạy bộ)
    3. Số vòng (số lần lặp lại)
    4. Mục tiêu hiệu suất cho nhịp tim (từ 90 đến 110 nhịp/phút)
  2. Thông tin này được nhóm thành các khối bài tập và bước tập, đồng thời được ứng dụng kế hoạch tập luyện ghi vào Health Connect dưới dạng PlannedExerciseSessionRecord.
  3. Người dùng thực hiện phiên đã lên kế hoạch (chạy).
  4. Dữ liệu tập thể dục liên quan đến phiên được ghi lại theo một trong hai cách:
    1. Bằng thiết bị đeo trong phiên hoạt động. Ví dụ: nhịp tim. Dữ liệu này được ghi vào Health Connect dưới dạng loại bản ghi cho hoạt động. Trong trường hợp này, HeartRateRecord.
    2. Do người dùng thực hiện theo cách thủ công sau phiên. Ví dụ: cho biết thời điểm bắt đầu và kết thúc của lượt chạy thực tế. Dữ liệu này được ghi vào Health Connect dưới dạng ExerciseSessionRecord.
  5. Sau đó, ứng dụng kế hoạch tập luyện sẽ đọc dữ liệu từ Health Connect để đánh giá hiệu suất thực tế so với các mục tiêu mà người dùng đặt ra trong phiên tập luyện đã lên kế hoạch.

Lập kế hoạch tập luyện và đặt mục tiêu

Người dùng có thể lên kế hoạch tập thể dục trong tương lai và đặt mục tiêu. Ghi dữ liệu này vào Health Connect dưới dạng một phiên tập thể dục theo kế hoạch.

Trong ví dụ được mô tả trong phần Ví dụ về cách sử dụng, người dùng lên kế hoạch chạy bộ 90 phút trong 2 ngày nữa. Buổi chạy này sẽ có 3 vòng quanh hồ với nhịp tim mục tiêu từ 90 đến 110 nhịp/phút.

Bạn có thể tìm thấy một đoạn mã như thế này trong trình xử lý biểu mẫu của một ứng dụng ghi lại các phiên tập thể dục đã lên kế hoạch vào Health Connect. Bạn cũng có thể tìm thấy thông tin này trong điểm tiếp nhận để tích hợp, chẳng hạn như với một dịch vụ cung cấp hoạt động đào tạo.

// 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()

Ghi lại dữ liệu về bài tập và hoạt động

Hai ngày sau, người dùng ghi lại phiên tập thể dục thực tế. Ghi dữ liệu này vào Health Connect dưới dạng một phiên tập thể dục.

Trong ví dụ này, thời lượng phiên của người dùng khớp chính xác với thời lượng đã lên kế hoạch.

Bạn có thể tìm thấy đoạn mã sau trong trình xử lý biểu mẫu của một ứng dụng ghi lại các phiên tập thể dục vào Health Connect. Bạn cũng có thể tìm thấy thông tin này trong các trình xử lý nhập và xuất dữ liệu cho thiết bị đeo có khả năng phát hiện và ghi lại các phiên tập thể dục.

insertedPlannedExerciseSessionId ở đây được dùng lại từ ví dụ trước. Trong một ứng dụng thực tế, mã nhận dạng sẽ được xác định bằng cách người dùng chọn một phiên tập thể dục đã lên kế hoạch trong danh sách các phiên hiện có.

// 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))

Thiết bị đeo cũng ghi lại nhịp tim của người dùng trong suốt quá trình chạy. Bạn có thể dùng đoạn mã sau để tạo các bản ghi trong phạm vi mục tiêu.

Trong một ứng dụng thực tế, các phần chính của đoạn mã này có thể nằm trong trình xử lý cho một thông báo từ thiết bị đeo, thông báo này sẽ ghi dữ liệu đo lường vào Health Connect khi thu thập.

// 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))

Đánh giá mục tiêu hiệu suất

Vào ngày hôm sau khi người dùng tập thể dục, bạn có thể truy xuất bài tập đã ghi nhật ký, kiểm tra mọi mục tiêu tập thể dục đã lên kế hoạch và đánh giá các loại dữ liệu bổ sung để xác định xem người dùng có đạt được mục tiêu đã đặt hay không.

Một đoạn mã như thế này có thể được tìm thấy trong một công việc định kỳ để đánh giá các mục tiêu hiệu suất hoặc khi tải danh sách bài tập và hiển thị thông báo về các mục tiêu hiệu suất trong một ứng dụng.

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

Phiên hoạt động tập thể dục

Phiên hoạt động tập thể dục có thể gồm mọi hoạt động từ chạy bộ đến chơi cầu lông.

Ghi phiên hoạt động tập thể dục

Dưới đây là cách tạo yêu cầu chèn kèm theo dữ liệu trong phiên hoạt động:

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
        )
    )
}

Đọc phiên hoạt động tập thể dục

Dưới đây là ví dụ về cách đọc một phiên hoạt động tập thể dục:

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

Ghi dữ liệu loại phụ

Phiên hoạt động cũng có thể có dữ liệu loại phụ không bắt buộc, làm phiên phong phú thêm bằng thông tin bổ sung.

Ví dụ: các phiên hoạt động tập thể dục có thể có các lớp ExerciseSegment, ExerciseLapExerciseRoute:

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
)