Health Connect bietet den Datentyp Geplantes Training, mit dem Trainings-Apps Trainingspläne schreiben und Workout-Apps Trainingspläne lesen können. Aufgezeichnete Trainingseinheiten können für eine personalisierte Leistungsanalyse abgerufen werden, um Nutzern dabei zu helfen, ihre Trainingsziele zu erreichen.
Verfügbarkeit von Health Connect prüfen
Bevor deine App versucht, Health Connect zu verwenden, sollte sie prüfen, ob Health Connect auf dem Gerät des Nutzers verfügbar ist. Health Connect ist möglicherweise nicht auf allen Geräten vorinstalliert oder wurde deaktiviert.
Du kannst die Verfügbarkeit mit der Methode HealthConnectClient.getSdkStatus() prüfen.
Verfügbarkeit von Health Connect prüfen
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 }
Je nach dem von getSdkStatus() zurückgegebenen Status kannst du den Nutzer auffordern, Health Connect bei Bedarf im Google Play Store zu installieren oder zu aktualisieren.
Verfügbarkeit der Funktion
Um herauszufinden, ob das Gerät eines Nutzers Trainingspläne in Health Connect unterstützt, prüfe, ob auf dem ClientFEATURE_PLANNED_EXERCISE verfügbar ist:
if (healthConnectClient
.features
.getFeatureStatus(
HealthConnectFeatures.FEATURE_PLANNED_EXERCISE
) == HealthConnectFeatures.FEATURE_STATUS_AVAILABLE) {
// Feature is available
} else {
// Feature isn't available
}
Erforderliche Berechtigungen
Der Zugriff auf geplante Trainingseinheiten wird durch die folgenden Berechtigungen geschützt:
android.permission.health.READ_PLANNED_EXERCISEandroid.permission.health.WRITE_PLANNED_EXERCISE
Wenn du deiner App die Funktion für geplante Trainingseinheiten hinzufügen möchtest, fordere zuerst Berechtigungen für den Datentyp PlannedExerciseSession an.
Hier ist die Berechtigung, die du deklarieren musst, um geplante Trainingseinheiten schreiben zu können:
<application>
<uses-permission
android:name="android.permission.health.WRITE_PLANNED_EXERCISE" />
...
</application>
Um geplante Trainingseinheiten zu lesen, musst du die folgenden Berechtigungen anfordern:
<application>
<uses-permission
android:name="android.permission.health.READ_PLANNED_EXERCISE" />
...
</application>
Berechtigungen vom Nutzer anfordern
Nachdem du eine Clientinstanz erstellt hast, muss deine App Berechtigungen vom Nutzer anfordern. Nutzer müssen jederzeit die Möglichkeit haben, Berechtigungen zu erteilen oder abzulehnen. Erstelle dazu eine Reihe von Berechtigungen für die erforderlichen Datentypen. Achte darauf, dass die Berechtigungen im Set zuerst in deinem Android-Manifest deklariert werden.
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
kannst du prüfen, ob deiner App bereits die erforderlichen Berechtigungen erteilt wurden. Wenn nicht, fordere diese Berechtigungen mit
createRequestPermissionResultContract
an. Dadurch wird der Berechtigungsbildschirm von Health Connect angezeigt.
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.") } } }
Zugehörige Berechtigungen
Trainingspläne sind mit Trainingseinheiten verknüpft. Daher muss der Nutzer die Berechtigung zur Verwendung jedes Datensatztyps erteilen, der mit einem Trainingsplan verknüpft ist, um diese Funktion von Health Connect vollständig nutzen zu können.
Wenn in einem Trainingsplan beispielsweise die Herzfrequenz eines Nutzers während einer Reihe von Läufen gemessen wird, müssen der Entwickler möglicherweise die folgenden Berechtigungen deklarieren und der Nutzer sie erteilen, damit die Trainingseinheit geschrieben und die Ergebnisse für die spätere Auswertung gelesen werden können:
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
Oft ist die App, die Trainingspläne erstellt und die Leistung anhand von Plänen auswertet, jedoch nicht dieselbe wie die App, die Trainingspläne verwendet und tatsächliche Trainingsdaten schreibt. Je nach App-Typ sind nicht alle Lese- und Schreibberechtigungen erforderlich. Beispielsweise benötigst du möglicherweise nur die folgenden Berechtigungen für die einzelnen App-Typen:
| App für Trainingspläne | Workout-App |
|---|---|
WRITE_PLANNED_EXERCISE |
READ_PLANNED_EXERCISE |
READ_EXERCISE |
WRITE_EXERCISE |
READ_EXERCISE_ROUTES |
WRITE_EXERCISE_ROUTE |
READ_HEART_RATE |
WRITE_HEART_RATE |
Informationen in einem Datensatz für eine geplante Trainingseinheit
- Titel der Sitzung.
- Eine Liste mit Blöcken für geplante Trainingseinheiten.
- Start- und Endzeit der Sitzung.
- Trainingstyp.
- Notizen zur Aktivität.
- Metadaten.
- ID der abgeschlossenen Trainingseinheit: Diese wird automatisch geschrieben, nachdem eine Trainingseinheit abgeschlossen wurde, die mit dieser geplanten Trainingseinheit verknüpft ist.
Informationen in einem Datensatz für einen Block für geplante Trainingseinheiten
Ein Block für geplante Trainingseinheiten enthält eine Liste mit Trainingsschritten, um die Wiederholung verschiedener Schrittgruppen zu unterstützen (z. B. eine Abfolge von Armbeugen, Burpees und Crunches fünfmal hintereinander ausführen).
- Beschreibung des Blocks.
- Eine Liste mit Schritten für geplante Trainingseinheiten.
- Anzahl der Wiederholungen.
Informationen in einem Datensatz für einen Schritt für geplante Trainingseinheiten
- Beschreibung des Schritts.
- Trainingskategorie.
- Trainingstyp.
- Eine Liste mit Leistungszielen.
- Abschlussziel.
Unterstützte Aggregationen
Für diesen Datentyp werden keine Aggregationen unterstützt.
Nutzungsbeispiel
Angenommen, ein Nutzer plant einen 90-minütigen Lauf in zwei Tagen. Dieser Lauf umfasst drei Runden um einen See mit einer Zielherzfrequenz zwischen 90 und 110 Schlägen pro Minute.
- Eine geplante Trainingseinheit mit den folgenden Angaben wird vom Nutzer in einer App für Trainingspläne definiert:
- Geplanter Start und geplantes Ende des Laufs
- Trainingstyp (Laufen)
- Anzahl der Runden (Wiederholungen)
- Leistungsziel für die Herzfrequenz (zwischen 90 und 110 Schlägen pro Minute)
- Diese Informationen werden in Trainingsblöcke und ‑schritte gruppiert und von der App für Trainingspläne als
PlannedExerciseSessionRecordin Health Connect geschrieben. - Der Nutzer führt die geplante Trainingseinheit (Laufen) aus.
- Trainingsdaten zur Sitzung werden entweder:
- von einem Wearable während der Sitzung aufgezeichnet. Beispiel: Herzfrequenz.
Diese Daten werden als Datensatztyp für die Aktivität in Health Connect geschrieben. In diesem Fall
HeartRateRecord. - vom Nutzer nach der Sitzung manuell eingegeben. Beispiel: Start und Ende des tatsächlichen Laufs. Diese Daten werden als
ExerciseSessionRecordin Health Connect geschrieben.
- von einem Wearable während der Sitzung aufgezeichnet. Beispiel: Herzfrequenz.
Diese Daten werden als Datensatztyp für die Aktivität in Health Connect geschrieben. In diesem Fall
- Später liest die App für Trainingspläne Daten aus Health Connect, um die tatsächliche Leistung mit den Zielen zu vergleichen, die der Nutzer in der geplanten Trainingseinheit festgelegt hat.
Trainingseinheiten planen und Ziele festlegen
Ein Nutzer kann sein Training in der Zukunft planen und Ziele festlegen. Schreibe dies als geplante Trainingseinheit in Health Connect.
Im Beispiel unter Nutzungsbeispiel, plant der Nutzer einen 90-minütigen Lauf in zwei Tagen. Dieser Lauf umfasst drei Runden um einen See mit einer Zielherzfrequenz zwischen 90 und 110 Schlägen pro Minute.
Ein Snippet wie dieses kann im Formular-Handler für eine App gefunden werden, die geplante Trainingseinheiten in Health Connect protokolliert. Es kann auch im Aufnahmepunkt für Einbindungen gefunden werden, z. B. bei einem Dienst, der Training anbietet.
// 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) }
Trainings- und Aktivitätsdaten protokollieren
Zwei Tage später protokolliert der Nutzer die tatsächliche Trainingseinheit. Schreibe dies als Trainingseinheit in Health Connect.
In diesem Beispiel entsprach die Sitzungsdauer des Nutzers genau der geplanten Dauer.
Das folgende Snippet kann im Formular-Handler für eine App gefunden werden, die Trainingseinheiten in Health Connect protokolliert. Es kann auch in Datenaufnahme- und ‑exporthandlern für ein Wearable gefunden werden, das Trainingseinheiten erkennen und protokollieren kann.
insertedPlannedExerciseSessionId wird hier aus dem vorherigen Beispiel wiederverwendet. In einer echten App würde die ID bestimmt, indem der Nutzer eine geplante Trainingseinheit aus einer Liste vorhandener Sitzungen auswählt.
// 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))
Ein Wearable protokolliert auch die Herzfrequenz während des gesamten Laufs. Mit dem folgenden Snippet können Datensätze im Zielbereich generiert werden.
In einer echten App sind die wichtigsten Teile dieses Snippets möglicherweise im Handler für eine Nachricht von einem Wearable zu finden, das Messungen nach der Erfassung in Health Connect schreibt.
// 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))
Leistungsziele auswerten
Am Tag nach dem Training des Nutzers kannst du die protokollierte Trainingseinheit abrufen, nach Zielen für geplante Trainingseinheiten suchen und zusätzliche Datentypen auswerten, um festzustellen, ob die festgelegten Ziele erreicht wurden.
Ein Snippet wie dieses wird wahrscheinlich in einem periodischen Job gefunden, um Leistungsziele auszuwerten, oder beim Laden einer Liste von Trainingseinheiten und Anzeigen einer Benachrichtigung zu Leistungszielen in einer App.
// 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 } } } } } } } }
Trainingseinheiten
Trainingseinheiten können alles von Laufen bis Badminton umfassen.
Trainingseinheiten schreiben
So erstellst du eine Einfügungsanfrage, die eine Sitzung enthält:
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
)
)
}
Trainingseinheit lesen
Hier ein Beispiel für das Lesen einer Trainingseinheit:
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
}
}
Daten des Untertyps schreiben
Sitzungen können auch optionale Daten des Untertyps enthalten, die die Sitzung mit zusätzlichen Informationen ergänzen.
Trainingseinheiten können beispielsweise die Klassen ExerciseSegment, ExerciseLap und ExerciseRoute enthalten:
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()
)