המדריך הזה תואם לגרסה 1.1.0-alpha11 של Health Connect.
אפליקציית 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" // 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 }
בהתאם לסטטוס שמוחזר על ידי 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.
// 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)
)
אפשר להשתמש ב-getGrantedPermissions
כדי לבדוק אם האפליקציה כבר קיבלה את ההרשאות הנדרשות. אם לא, צריך להשתמש ב-createRequestPermissionResultContract
כדי לבקש את ההרשאות האלה. מוצג מסך ההרשאות של 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)
}
}
המשתמשים יכולים לתת או לבטל הרשאות בכל שלב, ולכן האפליקציה צריכה לבדוק מעת לעת אם ההרשאות ניתנו, ולטפל בתרחישים שבהם ההרשאה בוטלה.
הרשאות קשורות
תוכניות האימונים מקושרות לסשנים של תרגילים. לכן, המשתמש צריך לתת הרשאה לשימוש בכל סוג של רשומה שקשורה לתוכנית אימונים כדי להשתמש בתכונה הזו של 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 |
איזה מידע נכלל ברשומה של אימון מתוכנן
- השם של הסשן.
- רשימה של בלוקים מתוכננים של תרגילים.
- שעת ההתחלה ושעת הסיום של הסשן.
- סוג התרגיל.
- הערות לפעילות.
- מטא-נתונים.
- מזהה של סשן אימון שהושלם – המזהה הזה נכתב באופן אוטומטי אחרי שסשן אימון שקשור לסשן האימון המתוכנן הזה הושלם.
איזה מידע נכלל ברשומה של בלוק אימונים מתוכנן
בלוק של תרגילים מתוכננים מכיל רשימה של שלבי תרגיל, כדי לתמוך בחזרה על קבוצות שונות של שלבים (לדוגמה, ביצוע רצף של כפיפות מרפקים, בורפי וכפיפות בטן חמש פעמים ברציפות).
- תיאור הבלוק.
- רשימה של שלבים מתוכננים של אימון כושר.
- מספר החזרות.
המידע שכלול ברשומה של שלב מתוכנן באימון
- תיאור השלב.
- קטגוריית הפעילות הגופנית.
- סוג התרגיל.
- רשימה של יעדי ביצועים.
- יעד השלמה.
צבירות נתמכות
אין צבירות נתמכות לסוג הנתונים הזה.
דוגמה לשימוש
נניח שמשתמש מתכנן ריצה של 90 דקות בעוד יומיים. באימון הזה תרוצו שלוש הקפות מסביב לאגם, עם דופק מטרה בין 90 ל-110 פעימות לדקה.
- משתמש מגדיר באפליקציית תוכנית אימונים סשן אימונים מתוכנן עם הפרטים הבאים:
- התחלה וסיום מתוכננים של הריצה
- סוג הפעילות הגופנית (ריצה)
- מספר ההקפות (חזרות)
- יעד הביצועים לדופק (בין 90 ל-110 פעימות בדקה)
- המידע הזה מקובץ לבלוקים של תרגילים וצעדים, ונכתב ב-Health Connect על ידי אפליקציית תוכנית האימונים כ
PlannedExerciseSessionRecord
. - המשתמש מבצע את הסשן המתוכנן (פועל).
- נתוני הפעילות הגופנית שקשורים לסשן נרשמים באחת מהדרכים הבאות:
- על ידי גאדג'ט לביש במהלך הסשן. לדוגמה, הדופק.
הנתונים האלה נכתבים ב-Health Connect כסוג הרשומה של הפעילות. במקרה הזה,
HeartRateRecord
. - באופן ידני על ידי המשתמש אחרי הסשן. לדוגמה, ציון ההתחלה והסיום של הריצה בפועל. הנתונים האלה נכתבים ב-Health
Connect כ
ExerciseSessionRecord
.
- על ידי גאדג'ט לביש במהלך הסשן. לדוגמה, הדופק.
הנתונים האלה נכתבים ב-Health Connect כסוג הרשומה של הפעילות. במקרה הזה,
- בשלב מאוחר יותר, אפליקציית תוכנית האימונים קוראת נתונים מ-Health Connect כדי להעריך את הביצועים בפועל בהשוואה ליעדים שהוגדרו על ידי המשתמש בסשן האימונים המתוכנן.
תכנון תרגילים והגדרת יעדים
משתמש יכול לתכנן את האימון שלו לעתיד ולהגדיר יעדים. תכתוב את זה ב-Health Connect כסשן אימון מתוכנן.
בדוגמה שמתוארת בקטע דוגמה לשימוש, המשתמש מתכנן הפעלה של 90 דקות בעוד יומיים. באימון הזה תרוצו שלוש הקפות סביב אגם, עם דופק מטרה בין 90 ל-110 פעימות בדקה.
קטע קוד כזה יכול להימצא ב-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.
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()
רישום נתוני פעילות ואימונים
יומיים לאחר מכן, המשתמש מתעד את אימון הכושר בפועל. לכתוב את זה ב-Health Connect כנתוני פעילות.
בדוגמה הזו, משך הסשן של המשתמש תאם בדיוק למשך המתוכנן.
יכול להיות שתמצאו את קטע הקוד הבא ב-handler של טופס באפליקציה שמתעדת סשנים של אימונים ב-Health Connect. יכול להיות שהיא תופיע גם בנתונים שמועברים לטיפול ובנתונים שמיוצאים ממכשיר לביש שיכול לזהות ולתעד אימונים.
הערך 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.manualEntry(
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.autoRecorded(
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"
),
// ... 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
)