طرح‌های تمرین

این راهنما با Health Connect نسخه 1.1.0-alpha12 یا بالاتر سازگار است.

Health Connect نوع داده تمرین برنامه‌ریزی‌شده را ارائه می‌دهد تا برنامه‌های تمرینی بتوانند برنامه‌های تمرین را بنویسند و برنامه‌های تمرین بتوانند برنامه‌های تمرین را بخوانند. تمرین‌های ضبط‌شده (تمرینات) را می‌توان برای تحلیل عملکرد شخصی‌سازی‌شده بازخوانی کرد تا به کاربران کمک کند به اهداف تمرینی خود برسند.

بررسی دردسترس بودن Health Connect

قبل‌از تلاش برای استفاده از Health Connect، برنامه‌تان باید بررسی کند که Health Connect در دستگاه کاربر دردسترس باشد. ممکن است Health Connect در همه دستگاه‌ها ازپیش نصب نشده باشد یا غیرفعال باشد. می‌توانید بااستفاده از روش HealthConnectClient.getSdkStatus() دردسترس بودن را بررسی کنید.

نحوه بررسی دردسترس بودن Health Connect

fun checkHealthConnectAvailability(context: Context) {
    val providerPackageName = "com.google.android.apps.healthdata"
    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
}

بسته به وضعیتی که getSdkStatus() برمی‌گرداند، می‌توانید کاربر را راهنمایی کنید تا درصورت لزوم Health Connect را از «فروشگاه Google Play» نصب یا به‌روز کند.

دردسترس بودن ویژگی

برای تعیین اینکه آیا دستگاه کاربر از طرح‌های تمرینی در 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 شما اعلام شده باشد.

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 نمایش داده می‌شود.
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.") }
    }
}
ازآنجایی‌که کاربران می‌توانند در هر زمانی اجازه‌ها را اعطا یا لغو کنند، برنامه شما باید قبل‌از استفاده از اجازه‌ها، هر بار آن‌ها را بررسی کند و سناریوهایی را که در آن‌ها اجازه ازدست رفته است مدیریت کند.

طرح‌های تمرین به جلسه‌های تمرین پیوند داده شده‌اند. بنابراین، کاربر باید برای استفاده از هر نوع سابقه مربوط به طرح تمرین اجازه دهد تا بتواند به‌طور کامل از این ویژگی 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

اطلاعات موجود در سابقه جلسه تمرین برنامه‌ریزی‌شده

  • عنوان جلسه.
  • فهرستی از بخش‌های تمرین برنامه‌ریزی‌شده.
  • زمان شروع و پایان جلسه.
  • نوع تمرین.
  • یادداشت‌های مربوط به فعالیت.
  • فراداده.
  • شناسه جلسه تمرین تکمیل‌شده — این شناسه به‌طور خودکار پس‌از تکمیل جلسه تمرین مربوط به این جلسه تمرین برنامه‌ریزی‌شده نوشته می‌شود.

اطلاعات موجود در سابقه بلوک تمرین برنامه‌ریزی‌شده

بلوک تمرین برنامه‌ریزی‌شده حاوی فهرستی از مراحل تمرین است تا از تکرار گروه‌های مختلف مراحل پشتیبانی کند (برای مثال، انجام توالی جلو بازو، برپی، و درازنشست به‌صورت پنج بار پشت‌سرهم).

اطلاعات موجود در سابقه مرحله تمرین برنامه‌ریزی‌شده

تجمیع‌های پشتیبانی‌شده

هیچ تجمیع پشتیبانی‌شده‌ای برای این نوع داده وجود ندارد.

نمونه استفاده

فرض کنید کاربری برای دو روز آینده یک دویدن ۹۰ دقیقه‌ای برنامه‌ریزی می‌کند. این دویدن شامل سه دور اطراف دریاچه با ضربان قلب هدف بین ۹۰ و ۱۱۰ ضربه در دقیقه خواهد بود.

  1. جلسه تمرین برنامه‌ریزی‌شده با موارد زیر توسط کاربر در برنامه طرح تمرین تعریف شده است:
    1. شروع و پایان برنامه‌ریزی‌شده برای اجرا
    2. نوع تمرین (دویدن)
    3. تعداد دورها (تکرارها)
    4. هدف عملکرد برای ضربان قلب (بین ۹۰ و ۱۱۰ ضربه در دقیقه)
  2. این اطلاعات در قالب بلوک‌ها و مراحل تمرین گروه‌بندی می‌شود و برنامه طرح تمرین آن را به‌عنوان PlannedExerciseSessionRecord در Health Connect می‌نویسد.
  3. کاربر جلسه برنامه‌ریزی‌شده (دویدن) را انجام می‌دهد.
  4. داده‌های تمرین مربوط به جلسه به یکی از این دو روش ضبط می‌شود:
    1. توسط دستگاه پوشیدنی درطول جلسه. برای مثال، ضربان قلب. این داده‌ها به‌عنوان نوع سابقه فعالیت در Health Connect نوشته می‌شود. در این صورت، HeartRateRecord.
    2. به‌صورت دستی توسط کاربر پس‌از جلسه. برای مثال، نشان دادن شروع و پایان اجرای واقعی. این داده‌ها به‌عنوان ExerciseSessionRecord در Health Connect نوشته می‌شود.
  5. بعداً، برنامه طرح تمرین داده‌ها را از Health Connect می‌خواند تا عملکرد واقعی را درمقابل اهداف تعیین‌شده توسط کاربر در جلسه تمرین برنامه‌ریزی‌شده ارزیابی کند.

برنامه‌ریزی تمرینات و تعیین اهداف

کاربر ممکن است برای تمرینات خود در آینده برنامه‌ریزی کند و اهدافی را تعیین کند. این را به‌عنوان جلسه تمرین برنامه‌ریزی‌شده در Health Connect بنویسید.

در مثال شرح‌داده‌شده در نمونه استفاده، کاربر برای دو روز آینده یک دویدن ۹۰ دقیقه‌ای برنامه‌ریزی می‌کند. این دویدن شامل سه دور اطراف دریاچه با ضربان قلب هدف بین ۹۰ و ۱۱۰ ضربه در دقیقه خواهد بود.

قطعه‌ای مانند این ممکن است در کنترل‌کننده فرم برای برنامه‌ای که جلسات تمرین برنامه‌ریزی‌شده را در 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.
    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.activelyRecorded(
        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)
}

ثبت داده‌های تمرین و فعالیت

دو روز بعد، کاربر جلسه تمرین واقعی را ثبت می‌کند. این را به‌عنوان جلسه تمرین در Health Connect بنویسید.

در این مثال، مدت جلسه کاربر دقیقاً با مدت برنامه‌ریزی‌شده مطابقت داشت.

قطعه کد زیر ممکن است در کنترل‌کننده فرم برای برنامه‌ای که جلسات تمرین را در Health Connect ثبت می‌کند، یافت شود. همچنین ممکن است در گرداننده‌های دریافت و صادر کردن داده برای دستگاه پوشیدنی‌ای که قادر به تشخیص و ثبت جلسه‌های تمرین است یافت شود.

insertedPlannedExerciseSessionId here is reused from the previous example. در یک برنامه واقعی، شناسه توسط کاربر با انتخاب یک جلسه تمرین برنامه‌ریزی‌شده از فهرست جلسات موجود تعیین می‌شود.

// 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.activelyRecorded(
        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.activelyRecorded(
        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",
                metadata = Metadata.manualEntry()
            ),
            // ... 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,
  metadata = Metadata.manualEntry()
)