תוכניות אימונים

המדריך הזה תואם לגרסה 1.1.0-alpha11 של Health Connect.

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 Console, וגם במניפסט של האפליקציה:

<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

המידע שכלול ברשומה של סשן אימון מתוכנן

  • שם הסשן.
  • רשימה של קטעי אימון מתוכננים.
  • שעת ההתחלה ושעת הסיום של הסשן.
  • סוג הפעילות הגופנית.
  • הערות לגבי הפעילות.
  • מטא-נתונים.
  • מזהה סשן האימון שהושלם – המזהה הזה נכתב באופן אוטומטי אחרי סיום סשן אימון שקשור לסשן האימון המתוכנן הזה.

המידע שכלול ברשומה של בלוק תרגילים מתוכנן

בלוק תרגילים מתוכנן מכיל רשימה של שלבי תרגילים, כדי לאפשר חזרה על קבוצות שונות של שלבים (לדוגמה, לבצע רצף של כפיפות מרפק, ברכיות ופלטס חמישה פעמים ברציפות).

המידע שכלול ברשומה של שלב באימון מתוכנן

צבירות נתמכות

אין צבירות נתמכות לסוג הנתונים הזה.

דוגמה לשימוש

נניח שמשתמש מתכנן ריצה של 90 דקות בעוד יומיים. הריצה הזו תכלול שלושה סבבים סביב אגם, עם דופק יעד של 90 עד 110 פעימות לדקה.

  1. סשן אימון מתוכנן עם הפרטים הבאים מוגדר על ידי המשתמש באפליקציית תוכנית אימונים:
    1. שעת ההתחלה והסיום המתוכננות של הריצה
    2. סוג הפעילות הגופנית (ריצה)
    3. מספר הקפות (חזרות)
    4. יעד הביצועים של הדופק (בין 90 ל-110 פעימות בדקה)
  2. המידע הזה מקובץ בבלוק של תרגילים ובשלבים, והאפליקציה של תוכנית האימון כותבת אותו ב-Health Connect בתור PlannedExerciseSessionRecord.
  3. המשתמש מבצע את הסשן המתוכנן (בפעילות).
  4. נתוני הפעילות הגופנית שקשורים לסשן מתועדים באחת מהדרכים הבאות:
    1. על ידי מכשיר לביש במהלך הסשן. לדוגמה, דופק. הנתונים האלה נכתבים ב-Health Connect כסוג הרשומה של הפעילות. במקרה הזה, HeartRateRecord.
    2. באופן ידני על ידי המשתמש אחרי הסשן. לדוגמה, לציין את תחילת הריצה ואת סופה בפועל. הנתונים האלה נכתבים ב-Health Connect כ-ExerciseSessionRecord.
  5. בשלב מאוחר יותר, אפליקציית תוכנית האימון קוראת נתונים מ-Health Connect כדי להעריך את הביצועים בפועל בהשוואה ליעדים שהמשתמש קבע בסשן האימון המתוכנן.

תכנון תרגילים והגדרת יעדים

המשתמש יכול לתכנן את האימון שלו בעתיד ולהגדיר יעדים. כותבים את זה ב-Health Connect כסשן אימון מתוכנן.

בדוגמה שמתוארת בקטע דוגמה לשימוש, המשתמש מתכנן ריצה של 90 דקות בעוד יומיים. הריצה הזו תכלול שלוש הקפות סביב אגם, עם יעד דופק של 90 עד 110 פעימות בדקה.

קטע קוד כזה עשוי להופיע במטפל בטופס של אפליקציה שמתעדת סשנים מתוכננים של אימון ב-Health Connect. הוא יכול להופיע גם בנקודת הטמעת הנתונים (ingest) של השילובים, למשל עם שירות שמציע הדרכה.

// 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 כאן משמש שוב מהדוגמה הקודמת. באפליקציה אמיתית, המזהה יקבע על ידי המשתמש שיבחר סשן אימון מתוכנן מרשימת סשנים קיימים.

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