המדריך הזה תואם לגרסה 1.1.0-alpha12 של Health Connect ואילך.
אפליקציית Health Connect מספקת סוג נתונים של אימון מתוכנן שמאפשר לאפליקציות אימון לכתוב תוכניות אימונים ולאפליקציות כושר לקרוא תוכניות אימונים. אפשר לקרוא את האימונים המתועדים כדי לנתח את הביצועים באופן מותאם אישית ולעזור למשתמשים להשיג את יעדי האימון שלהם.
איך בודקים אם אפליקציית Health Connect זמינה
לפני שמנסים להשתמש ב-Health Connect, האפליקציה צריכה לוודא שהשירות זמין במכשיר של המשתמש. יכול להיות שאפליקציית Health Connect לא מותקנת מראש בחלק מהמכשירים או שהיא מושבתת.
אפשר לבדוק את הזמינות באמצעות ה-method 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_EXERCISEandroid.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_EXERCISEandroid.permission.health.READ_EXERCISE_ROUTESandroid.permission.health.READ_HEART_RATEandroid.permission.health.WRITE_EXERCISEandroid.permission.health.WRITE_EXERCISE_ROUTEandroid.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 |
המידע שכלול ברשומה של אימון מתוכנן
- שם הסשן.
- רשימה של משבצות זמן לאימוני כושר מתוכננים.
- שעת ההתחלה והסיום של הסשן.
- סוג התרגיל.
- הערות לגבי הפעילות.
- מטא-נתונים.
- מזהה של אימון שהושלם – נכתב באופן אוטומטי אחרי שאימון שקשור לאימון המתוכנן הזה הושלם.
המידע שכלול ברשומה של משבצת זמן לאימון מתוכנן
בלוק של אימון מתוכנן מכיל רשימה של שלבי אימון, כדי לתמוך בחזרה על קבוצות שונות של שלבים (לדוגמה, לבצע רצף של כפיפות זרוע, סמוך קום וכפיפות בטן חמש פעמים ברציפות).
- תיאור הבלוק.
- רשימה של שלבי אימון מתוכננים.
- מספר החזרות.
המידע שכלול ברשומה של שלב באימון מתוכנן
- תיאור השלב.
- קטגוריית הפעילות הגופנית.
- סוג התרגיל.
- רשימה של יעדי ביצועים.
- יעד השלמה.
צבירת נתונים נתמכת
אין צבירות נתונים נתמכות לסוג הנתונים הזה.
דוגמת שימוש
נניח שמשתמש מתכנן ריצה של 90 דקות בעוד יומיים. הריצה הזו תכלול שלוש הקפות סביב אגם עם דופק מטרה בין 90 ל-110 פעימות לדקה.
- אימון מתוכנן עם הפרטים הבאים מוגדר על ידי המשתמש באפליקציה של תוכנית אימונים:
- התחלה וסיום מתוכננים של ההרצה
- סוג הפעילות הגופנית (ריצה)
- מספר ההקפות (חזרות)
- יעד ביצועים לדופק (בין 90 ל-110 פעימות לדקה)
- המידע הזה מקובץ לתרגילים ולצעדים, ונכתב ב-Health Connect על ידי אפליקציית תוכנית האימונים בתור
PlannedExerciseSessionRecord. - המשתמש מבצע את האימון המתוכנן (ריצה).
- נתוני הפעילות הגופנית שקשורים לסשן מתועדים באחת מהדרכים הבאות:
- על ידי גאדג'ט לביש במהלך הפעילות. לדוגמה, דופק.
הנתונים האלה נכתבים ב-Health Connect כסוג הרשומה של הפעילות. במקרה הזה,
HeartRateRecord. - באופן ידני על ידי המשתמש אחרי הסשן. לדוגמה, ציון ההתחלה והסיום של הריצה בפועל. הנתונים האלה נכתבים ב-Health Connect כ
ExerciseSessionRecord.
- על ידי גאדג'ט לביש במהלך הפעילות. לדוגמה, דופק.
הנתונים האלה נכתבים ב-Health Connect כסוג הרשומה של הפעילות. במקרה הזה,
- בשלב מאוחר יותר, האפליקציה של תוכנית האימונים קוראת נתונים מ-Health Connect כדי להעריך את הביצועים בפועל בהשוואה ליעדים שהמשתמש הגדיר באימון המתוכנן.
תכנון אימונים והגדרת יעדים
המשתמש יכול לתכנן את האימון שלו בעתיד ולהגדיר יעדים. כותבים את זה ב-Health Connect בתור אימון מתוכנן.
בדוגמה שמתוארת בקטע דוגמה לשימוש, המשתמש מתכנן ריצה של 90 דקות בעוד יומיים. הריצה הזו תכלול שלוש הקפות סביב אגם עם דופק מטרה של בין 90 ל-110 פעימות לדקה.
קטע קוד כזה יכול להופיע ב-form handler של אפליקציה שמתעדת אימונים מתוכננים ב-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 בתור סשן אימון.
בדוגמה הזו, משך הסשן של המשתמש תאם בדיוק למשך הזמן המתוכנן.
קטע הקוד הבא יכול להופיע ב-form handler של אפליקציה שמתעדת אימונים ב-Health Connect. הוא יכול להופיע גם ב-handlers של הטמעת נתונים ושל ייצוא נתונים עבור מכשיר לביש שמסוגל לזהות ולתעד אימונים.
הערך insertedPlannedExerciseSessionId כאן הוא שימוש חוזר מהדוגמה הקודמת. באפליקציה אמיתית, המזהה ייקבע על ידי המשתמש שיבחר אימון מתוכנן מתוך רשימה של אימונים קיימים.
// 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))
מכשיר לביש מתעד גם את הדופק שלהם במהלך הריצה. אפשר להשתמש בקטע הקוד הבא כדי ליצור רשומות בטווח היעד.
באפליקציה אמיתית, החלקים העיקריים של קטע הקוד הזה עשויים להימצא ב-handler של הודעה ממכשיר לביש, שיכתוב את המדידה ל-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()
)