Kế hoạch luyện tập

Health Connect cung cấp kiểu dữ liệu bài tập theo kế hoạch để cho phép ứng dụng huấn luyện ghi kế hoạch huấn luyện và cho phép ứ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 thể dục) để phân tích hiệu suất theo từng người dùng nhằm giúp người dùng đạt được mục tiêu huấn luyện.

Kiểm tra phạm vi cung cấp Health Connect

Trước khi cố gắng sử dụng Health Connect, ứng dụng của bạn phải xác minh rằng Health Connect có trên thiết bị của người dùng. Health Connect có thể không được cài đặt sẵn trên một số thiết bị hoặc có thể bị tắt. Bạn có thể kiểm tra phạm vi cung cấp bằng phương thức HealthConnectClient.getSdkStatus().

Cách kiểm tra phạm vi cung cấp Health Connect

fun checkHealthConnectAvailability(context: Context) {
    val providerPackageName = "com.google.android.apps.healthdata" // Or get from HealthConnectClient.DEFAULT_PROVIDER_PACKAGE_NAME
    val availabilityStatus = HealthConnectClient.getSdkStatus(context, providerPackageName)

    if (availabilityStatus == HealthConnectClient.SDK_UNAVAILABLE) {
      // Health Connect is not available. Guide the user to install/enable it.
      // For example, show a dialog.
      return // early return as there is no viable integration
    }
    if (availabilityStatus == HealthConnectClient.SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED) {
      // Health Connect is available but requires an update.
      // Optionally redirect to package installer to find a provider, for example:
      val uriString = "market://details?id=$providerPackageName&url=healthconnect%3A%2F%2Fonboarding"
      context.startActivity(
        Intent(Intent.ACTION_VIEW).apply {
          setPackage("com.android.vending")
          data = Uri.parse(uriString)
          putExtra("overlay", true)
          putExtra("callerId", context.packageName)
        }
      )
      return
    }
    // Health Connect is available, obtain a HealthConnectClient instance
    val healthConnectClient = HealthConnectClient.getOrCreate(context)
    // Issue operations with healthConnectClient
}

Tuỳ thuộc vào trạng thái do getSdkStatus() trả về, bạn có thể hướng dẫn người dùng cài đặt hoặc cập nhật Health Connect trên Cửa hàng Google Play nếu cầ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 huấn luyện trên Health Connect hay không, hãy kiểm tra phạm vi 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 bài viết Kiểm tra phạm vi cung cấp 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 theo 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 khả năng tập thể dục theo 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 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 theo kế hoạch:

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

Để đọc bài tập theo 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 thực thể ứ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.

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.
val permissions = setOf(
        HealthPermission.getReadPermission(StepsRecord::class),
        HealthPermission.getWritePermission(StepsRecord::class),
        HealthPermission.getReadPermission(HeartRateRecord::class),
        HealthPermission.getWritePermission(HeartRateRecord::class)
    )

val requestPermissionsLauncher = rememberLauncherForActivityResult(
    contract = PermissionController.createRequestPermissionResultContract()
) { grantedPermissions ->
    if (grantedPermissions.containsAll(permissions)) {
        coroutineScope.launch { snackbarHostState.showSnackbar("Permissions granted!") }
    } else {
        coroutineScope.launch { snackbarHostState.showSnackbar("Permissions denied.") }
    }
}
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 quyền mỗi khi sử dụng và xử lý các trường hợp mất quyền.

Kế hoạch huấn luyện được liên kết với phiên hoạt động 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 huấn luyện để tận dụng tối đa tính năng này của Health Connect.

Ví dụ: nếu kế hoạch huấn luyện đo nhịp tim của người dùng trong một loạt lần chạy, thì nhà phát triển có thể cần khai báo và người dùng cần cấp các quyền sau để ghi phiên hoạt động 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 huấn luyện và đánh giá hiệu suất theo kế hoạch không giống với ứng dụng sử dụng kế hoạch huấn luyện và ghi dữ liệu bài tập thực tế. Tuỳ thuộc vào loại ứng dụng, bạn có thể không cần tất cả quyền đọc và ghi. Ví dụ: bạn có thể chỉ cần các quyền này cho từng loại ứng dụng:

Ứng dụng kế hoạch huấn luyện Ứng dụng tập luyện
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 phiên hoạt động tập thể dục theo kế hoạch

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

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

Khối bài tập theo 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 động tác gập tay, động tác burpee và động tác gập bụng 5 lần liên tiếp).

  • Nội dung mô tả về khối.
  • Danh sách các bước tập thể dục theo kế hoạch .
  • Số lần lặp lại.

Thông tin có trong bản ghi bước tập thể dục theo 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 kiểu dữ liệu này.

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

Giả sử người dùng lên kế hoạch chạy bộ 90 phút trong 2 ngày tới. Lần 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. Phiên hoạt động tập thể dục theo kế hoạch có các thông tin sau do người dùng xác định trong ứng dụng kế hoạch huấn 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 tần số tim (từ 90 đến 110 nhịp/phút)
  2. Thông tin này được nhóm thành các khối và bước tập thể dục, đồng thời được ứng dụng kế hoạch huấn luyện ghi vào Health Connect dưới dạng PlannedExerciseSessionRecord.
  3. Người dùng thực hiện phiên hoạt động theo kế hoạch (chạy bộ).
  4. Dữ liệu bài tập liên quan đến phiên hoạt động đượ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ụ: tần số 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, đó là HeartRateRecord.
    2. Thủ công do người dùng thực hiện sau phiên hoạt động. Ví dụ: cho biết thời gian bắt đầu và kết thúc của lần chạy thực tế. Dữ liệu này được ghi vào Health Connect dưới dạng ExerciseSessionRecord.
  5. Vào một thời điểm sau đó, ứng dụng kế hoạch huấn 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 do người dùng đặt trong phiên hoạt động tập thể dục theo kế hoạch.

Lập kế hoạch tập thể dục 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. Hãy ghi thông tin này vào Health Connect dưới dạng phiên hoạt động 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 tới. Lần 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.

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 cho ứng dụng ghi lại các phiên hoạt động tập thể dục theo kế hoạch vào Health Connect. Bạn cũng có thể tìm thấy đoạn mã này ở điểm tiếp nhận cho các hoạt động tích hợp, chẳng hạn như với một dịch vụ cung cấp hoạt động huấn luyện.

// 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.
    Log.w("HealthConnect", "Write permission for PlannedExerciseSessionRecord not granted.")
    return
}

val plannedExerciseSessionRecord = PlannedExerciseSessionRecord(
    startTime = startTime,
    endTime = endTime,
    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(
        device = Device(type = Device.Companion.TYPE_PHONE),
    ),
    startZoneOffset = null,
    endZoneOffset = null,
)

try {
    // Attempt to insert the record
    val response = healthConnectClient.insertRecords(listOf(plannedExerciseSessionRecord))

    // If execution reaches here, the insert succeeded.
    // Safely extract the ID using firstOrNull()
    val insertedPlannedExerciseSessionId = response.recordIdsList.firstOrNull()

    if (insertedPlannedExerciseSessionId != null) {
        Log.d("HealthConnect", "Successfully inserted planned exercise session ID: $insertedPlannedExerciseSessionId")
    } else {
        Log.w("HealthConnect", "Insertion succeeded but no record IDs were returned.")
    }

} catch (e: Exception) {
    // Handle API failures, database errors, or system issues safely without crashing
    Log.e("HealthConnect", "Failed to insert planned exercise session record", e)
}

Ghi nhật ký dữ liệu bài tập và hoạt động

Hai ngày sau, người dùng ghi nhật ký phiên hoạt động tập thể dục thực tế. Hãy ghi thông tin này vào Health Connect dưới dạng phiên hoạt động tập thể dục.

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

Bạn có thể tìm thấy đoạn mã sau trong trình xử lý biểu mẫu cho ứng dụng ghi lại các phiên hoạt động tập thể dục vào Health Connect. Bạn cũng có thể tìm thấy đoạn mã này trong trình xử lý tiếp nhận 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 hoạt động 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ã này sẽ được xác định bằng cách người dùng chọn một phiên hoạt động tập thể dục theo kế hoạch trong danh sách các phiên hoạt động 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(
        device = Device(type = Device.Companion.TYPE_PHONE)
    )
)
val insertedExerciseSessions =
    healthConnectClient.insertRecords(listOf(exerciseSessionRecord))

Thiết bị đeo cũng ghi lại tần số tim của người dùng trong suốt quá trình chạy. Bạn có thể sử dụng đoạn mã sau để tạo 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ể được tìm thấy trong trình xử lý cho một thông báo từ thiết bị đeo. Trình xử lý 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(
        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 sau khi người dùng tập luyện, 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 theo kế hoạch và đánh giá các kiểu dữ liệu bổ sung để xác định xem các mục tiêu đã đặt có đạt được hay không.

Bạn có thể tìm thấy một đoạn mã như thế này trong một công việc định kỳ để đánh giá 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ề 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",
                metadata = Metadata.manualEntry()
            ),
            // ... 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,
  metadata = Metadata.manualEntry()
)