Diese Anleitung ist mit Health Connect-Version 1.1.0-alpha12 oder höher kompatibel.
Health Connect bietet den Datentyp Geplantes Training, damit Trainings-Apps Trainingspläne schreiben und Workout-Apps Trainingspläne lesen können. Aufgezeichnete Trainings (Workouts) können für eine personalisierte Leistungsanalyse ausgelesen werden, um Nutzern zu helfen, ihre Trainingsziele zu erreichen.
Verfügbarkeit von Health Connect prüfen
Bevor du versuchst, Health Connect zu verwenden, sollte deine App 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 deaktiviert.
Sie können 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" 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 bei Bedarf anleiten, Health Connect aus dem 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üfen Sie, obFEATURE_PLANNED_EXERCISE auf dem Client 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 Trainings wird durch die folgenden Berechtigungen geschützt:
android.permission.health.READ_PLANNED_EXERCISEandroid.permission.health.WRITE_PLANNED_EXERCISE
Wenn du deiner App die Möglichkeit hinzufügen möchtest, geplante Trainings zu erstellen, musst du zuerst Berechtigungen für den Datentyp PlannedExerciseSession anfordern.
Hier ist die Berechtigung, die Sie deklarieren müssen, um „Geplantes Training“ schreiben zu können:
<application>
<uses-permission
android:name="android.permission.health.WRITE_PLANNED_EXERCISE" />
...
</application>
Wenn du geplante Trainings lesen möchtest, musst du die folgenden Berechtigungen anfordern:
<application>
<uses-permission
android:name="android.permission.health.READ_PLANNED_EXERCISE" />
...
</application>
Berechtigungen vom Nutzer anfordern
Nachdem Sie eine Clientinstanz erstellt haben, muss Ihre App Berechtigungen vom Nutzer anfordern. Nutzer müssen Berechtigungen jederzeit erteilen oder verweigern können. Erstellen Sie dazu eine Reihe von Berechtigungen für die erforderlichen Datentypen. Achte darauf, dass die Berechtigungen in der Gruppe 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 gewährt wurden. Falls nicht, verwenden Sie createRequestPermissionResultContract, um diese Berechtigungen anzufordern. Der Bildschirm mit den Health Connect-Berechtigungen wird 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 aller Datensatztypen erteilen, die mit einem Trainingsplan verknüpft sind, um diese Funktion von Health Connect vollständig nutzen zu können.
Wenn ein Trainingsplan beispielsweise die Herzfrequenz eines Nutzers während einer Reihe von Läufen misst, müssen die folgenden Berechtigungen möglicherweise vom Entwickler deklariert und vom Nutzer gewährt werden, damit die Trainingseinheit aufgezeichnet und die Ergebnisse für eine 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
Häufig ist die App, die Trainingspläne erstellt und die Leistung anhand von Plänen bewertet, jedoch nicht dieselbe App, die Trainingspläne nutzt und tatsächliche Trainingsdaten schreibt. Je nach App-Typ sind nicht alle Lese- und Schreibberechtigungen erforderlich. Für die einzelnen App-Typen benötigen Sie beispielsweise nur diese Berechtigungen:
| App für Trainingspläne | Fitness-App |
|---|---|
WRITE_PLANNED_EXERCISE |
READ_PLANNED_EXERCISE |
READ_EXERCISE |
WRITE_EXERCISE |
READ_EXERCISE_ROUTES |
WRITE_EXERCISE_ROUTE |
READ_HEART_RATE |
WRITE_HEART_RATE |
In einem Datensatz für eine geplante Trainingseinheit enthaltene Informationen
- Titel der Sitzung.
- Eine Liste mit geplanten Trainingsblöcken.
- Start- und Endzeit der Sitzung.
- Sportart.
- Notizen zur Aktivität.
- Metadaten:
- ID der abgeschlossenen Trainingseinheit: Diese wird automatisch geschrieben, nachdem eine Trainingseinheit im Zusammenhang mit dieser geplanten Trainingseinheit abgeschlossen wurde.
In einem Datensatz für einen geplanten Trainingsblock enthaltene Informationen
Ein geplanter Trainingsblock enthält eine Liste von Trainingsschritten, um die Wiederholung verschiedener Gruppen von Schritten zu unterstützen (z. B. fünfmal hintereinander eine Abfolge von Arm-Curls, Burpees und Crunches ausführen).
- Beschreibung des Blocks.
- Eine Liste der geplanten Trainingsschritte.
- Anzahl der Wiederholungen.
In einem Datensatz für einen geplanten Trainingsschritt enthaltene Informationen
- Beschreibung des Schritts.
- Trainingskategorie.
- Sportart:
- Eine Liste der Leistungsziele.
- Abschlussziel.
Unterstützte Aggregationen
Für diesen Datentyp gibt es keine unterstützten Aggregationen.
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 Elementen wird vom Nutzer in einer Trainingsplan-App definiert:
- Geplanter Start und geplantes Ende des Laufs
- Die Art des Trainings (Laufen)
- Anzahl der Runden (Wiederholungen)
- Leistungsziel für die Herzfrequenz (zwischen 90 und 110 bpm)
- Diese Informationen werden in Trainingsblöcke und ‑schritte gruppiert und von der Trainingsplan-App als
PlannedExerciseSessionRecordin Health Connect geschrieben. - Der Nutzer führt die geplante Trainingseinheit (Laufen) aus.
- Trainingsdaten im Zusammenhang mit der Trainingseinheit werden entweder so aufgezeichnet:
- Von einem Wearable während der Trainingseinheit. Zum Beispiel die Herzfrequenz.
Diese Daten werden in Health Connect als Datensatztyp für die Aktivität geschrieben. In diesem Fall ist das
HeartRateRecord. - Manuell durch den Nutzer nach der Sitzung. Beispielsweise können Sie den Beginn und das Ende des eigentlichen Laufs angeben. Diese Daten werden als
ExerciseSessionRecordin Health Connect geschrieben.
- Von einem Wearable während der Trainingseinheit. Zum Beispiel die Herzfrequenz.
Diese Daten werden in Health Connect als Datensatztyp für die Aktivität geschrieben. In diesem Fall ist das
- Zu einem späteren Zeitpunkt liest die Trainingsplan-App Daten aus Health Connect, um die tatsächliche Leistung mit den Zielen zu vergleichen, die der Nutzer in der geplanten Trainingseinheit festgelegt hat.
Übungen planen und Ziele festlegen
Ein Nutzer kann sein Training für die Zukunft planen und sich Ziele setzen. Schreibe dies als geplante Trainingseinheit in Health Connect.
Im Beispiel unter Beispielnutzung 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 bpm.
Ein Snippet wie dieses kann im Formular-Handler für eine App enthalten sein, die geplante Trainingseinheiten in Health Connect protokolliert. Sie könnte auch am Aufnahmepunkt für Integrationen zu finden sein, z. B. bei einem Dienst, der Schulungen 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.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) }
Trainings- und Aktivitätsdaten protokollieren
Zwei Tage später zeichnet der Nutzer die eigentliche Trainingseinheit auf. Schreiben Sie dies als Trainingseinheit in Health Connect.
In diesem Beispiel entsprach die Sitzungsdauer des Nutzers genau der geplanten Dauer.
Das folgende Snippet könnte im Formular-Handler für eine App enthalten sein, die Trainingseinheiten in Health Connect protokolliert. Sie können auch in Datenaufnahme- und Exporthandlern für ein Wearable enthalten sein, das Trainingseinheiten erkennen und protokollieren kann.
insertedPlannedExerciseSessionId wird hier aus dem vorherigen Beispiel wiederverwendet. In einer echten Anwendung würde die ID vom Nutzer bestimmt, der eine geplante Trainingseinheit aus einer Liste vorhandener Einheiten 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.activelyRecorded( device = Device(type = Device.Companion.TYPE_PHONE) ) ) val insertedExerciseSessions = healthConnectClient.insertRecords(listOf(exerciseSessionRecord))
Ein Wearable zeichnet während des Laufs auch die Herzfrequenz auf. Mit dem folgenden Snippet können Datensätze im Zielbereich generiert werden.
In einer echten App befinden sich die wichtigsten Teile dieses Snippets möglicherweise im Handler für eine Nachricht von einem Wearable, das die Messung 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.activelyRecorded( device = Device(type = Device.Companion.TYPE_WATCH) ) ) val insertedHeartRateRecords = healthConnectClient.insertRecords(listOf(heartRateRecord))
Leistungsziele bewerten
Am Tag nach dem Training des Nutzers kannst du das aufgezeichnete Training abrufen, nach geplanten Trainingszielen suchen und zusätzliche Datentypen auswerten, um festzustellen, ob die festgelegten Ziele erreicht wurden.
Ein solches Snippet wird wahrscheinlich in einem regelmäßigen Job verwendet, um Leistungsziele zu bewerten, oder wenn eine Liste von Übungen geladen und eine Benachrichtigung über Leistungsziele in einer App angezeigt wird.
// 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 erstellen Sie 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 die Interpretation 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
}
}
Untertypdaten schreiben
Sitzungen können auch optionale Untertypdaten enthalten, die die Sitzung mit zusätzlichen Informationen anreichern.
Beispielsweise können Trainingseinheiten 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()
)