Health Connect udostępnia typ danych zaplanowane ćwiczenia, aby umożliwić aplikacjom treningowym zapisywanie planów treningowych, a aplikacjom do ćwiczeń – odczytywanie tych planów. Zarejestrowane ćwiczenia (treningi) można odczytywać w celu spersonalizowanej analizy wydajności, aby pomóc użytkownikom w osiąganiu celów treningowych.
Sprawdzanie dostępności Health Connect
Zanim spróbujesz użyć Health Connect, sprawdź, czy jest on dostępny na urządzeniu użytkownika. Health Connect może nie być wstępnie zainstalowana na wszystkich urządzeniach lub może być wyłączona.
Dostępność możesz sprawdzić za pomocą HealthConnectClient.getSdkStatus()metody.
Jak sprawdzić dostępność 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 }
W zależności od stanu zwróconego przez getSdkStatus() możesz w razie potrzeby poprosić użytkownika o zainstalowanie lub zaktualizowanie Health Connect ze Sklepu Google Play.
Dostępność funkcji
Aby sprawdzić, czy urządzenie użytkownika obsługuje plany treningowe w Health Connect, sprawdź dostępnośćFEATURE_PLANNED_EXERCISE w aplikacji:
if (healthConnectClient
.features
.getFeatureStatus(
HealthConnectFeatures.FEATURE_PLANNED_EXERCISE
) == HealthConnectFeatures.FEATURE_STATUS_AVAILABLE) {
// Feature is available
} else {
// Feature isn't available
}
Wymagane uprawnienia
Dostęp do zaplanowanych ćwiczeń jest chroniony przez te uprawnienia:
android.permission.health.READ_PLANNED_EXERCISEandroid.permission.health.WRITE_PLANNED_EXERCISE
Aby dodać do aplikacji funkcję planowania ćwiczeń, zacznij od poproszenia o uprawnienia do typu danych PlannedExerciseSession.
Aby móc zapisywać zaplanowane ćwiczenia, musisz zadeklarować to uprawnienie:
<application>
<uses-permission
android:name="android.permission.health.WRITE_PLANNED_EXERCISE" />
...
</application>
Aby odczytać zaplanowane ćwiczenie, musisz poprosić o te uprawnienia:
<application>
<uses-permission
android:name="android.permission.health.READ_PLANNED_EXERCISE" />
...
</application>
Prośba użytkownika o uprawnienia
Po utworzeniu instancji klienta aplikacja musi poprosić użytkownika o uprawnienia. Użytkownicy muszą mieć możliwość przyznania lub odmowy przyznania uprawnień w dowolnym momencie. Aby to zrobić, utwórz zestaw uprawnień dla wymaganych typów danych. Sprawdź, czy uprawnienia w zestawie są najpierw zadeklarowane w pliku manifestu Androida.
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, aby sprawdzić, czy Twoja aplikacja ma już przyznane wymagane uprawnienia. Jeśli nie, użyj
createRequestPermissionResultContract
aby poprosić o te uprawnienia. Wyświetli się ekran uprawnień 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.") } } }
Powiązane uprawnienia
Plany treningowe są powiązane z sesjami ćwiczeń. Dlatego użytkownik musi przyznać uprawnienia do korzystania z każdego typu rekordu związanego z planem treningowym, aby w pełni korzystać z tej funkcji Health Connect.
Jeśli na przykład plan treningowy mierzy tętno użytkownika podczas serii biegów, deweloper może musieć zadeklarować te uprawnienia, a użytkownik musi je przyznać, aby można było zapisać sesję ćwiczeń i odczytać wyniki do późniejszej oceny:
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
Często jednak aplikacja, która tworzy plany treningowe i ocenia wyniki na ich podstawie, nie jest tą samą aplikacją, która wykorzystuje plany treningowe i zapisuje rzeczywiste dane o ćwiczeniach. W zależności od typu aplikacji nie wszystkie uprawnienia do odczytu i zapisu będą potrzebne. Na przykład w przypadku każdego typu aplikacji mogą być potrzebne tylko te uprawnienia:
| Aplikacja z planem treningowym | Aplikacja treningowa |
|---|---|
WRITE_PLANNED_EXERCISE |
READ_PLANNED_EXERCISE |
READ_EXERCISE |
WRITE_EXERCISE |
READ_EXERCISE_ROUTES |
WRITE_EXERCISE_ROUTE |
READ_HEART_RATE |
WRITE_HEART_RATE |
Informacje zawarte w rekordzie zaplanowanej sesji ćwiczeń
- Nazwa sesji.
- Lista zaplanowanych bloków ćwiczeń.
- czas rozpoczęcia i zakończenia sesji;
- Typ ćwiczenia.
- Notatki dotyczące aktywności.
- Metadane
- Identyfikator ukończonej sesji ćwiczeń – jest zapisywany automatycznie po ukończeniu sesji ćwiczeń powiązanej z tą zaplanowaną sesją.
Informacje zawarte w rekordzie zaplanowanego bloku ćwiczeń
Zaplanowany blok ćwiczeń zawiera listę kroków ćwiczeń, aby umożliwić powtarzanie różnych grup kroków (np. wykonanie sekwencji ugięć ramion, burpees i brzuszków 5 razy z rzędu).
- Opis bloku.
- Lista zaplanowanych kroków ćwiczeń.
- Liczba powtórzeń.
Informacje zawarte w rekordzie zaplanowanego kroku ćwiczenia
- Opis kroku.
- Kategoria ćwiczenia.
- Typ ćwiczenia
- Lista celów skuteczności.
- Cel ukończenia
Obsługiwane agregacje
W przypadku tego typu danych nie ma obsługiwanych agregacji.
Przykład użycia
Załóżmy, że użytkownik zaplanował 90-minutowy bieg za 2 dni. Ten bieg będzie obejmować 3 okrążenia wokół jeziora z docelowym tętnem od 90 do 110 uderzeń na minutę.
- Zaplanowana sesja ćwiczeń z tymi parametrami jest definiowana przez użytkownika w aplikacji do planowania treningów:
- Planowany początek i koniec przejazdu
- Rodzaj ćwiczenia (bieganie)
- Liczba okrążeń (powtórzeń)
- Docelowe tętno (90–110 uderz./min)
- Te informacje są grupowane w bloki ćwiczeń i kroków, a następnie zapisywane w Health Connect przez aplikację planu treningowego jako
PlannedExerciseSessionRecord. - Użytkownik przeprowadza zaplanowaną sesję (bieganie).
- Dane ćwiczeń związane z sesją są rejestrowane w jeden z tych sposobów:
- przez urządzenie do noszenia podczas sesji. Na przykład tętno.
Te dane są zapisywane w Health Connect jako typ rekordu dla aktywności. W tym przypadku
HeartRateRecord. - Ręcznie przez użytkownika po zakończeniu sesji. Na przykład wskazanie początku i końca rzeczywistego biegu. Te dane są zapisywane w Health Connect jako
ExerciseSessionRecord.
- przez urządzenie do noszenia podczas sesji. Na przykład tętno.
Te dane są zapisywane w Health Connect jako typ rekordu dla aktywności. W tym przypadku
- W późniejszym czasie aplikacja z planem treningowym odczytuje dane z Health Connect, aby ocenić rzeczywiste wyniki w porównaniu z celami wyznaczonymi przez użytkownika podczas zaplanowanej sesji ćwiczeń.
Planowanie ćwiczeń i wyznaczanie celów
Użytkownik może zaplanować ćwiczenia na przyszłość i ustawić cele. Zapisz to w Health Connect jako zaplanowaną sesję ćwiczeń.
W przykładzie opisanym w sekcji Przykłady użycia użytkownik planuje 90-minutowy bieg za 2 dni. Ten bieg będzie obejmować 3 okrążenia wokół jeziora z docelowym tętnem od 90 do 110 uderzeń na minutę.
Fragment kodu podobny do tego może znajdować się w programie obsługi formularzy aplikacji, która rejestruje zaplanowane sesje ćwiczeń w Health Connect. Może się też znajdować w punkcie odbioru danych na potrzeby integracji, np. z usługą oferującą szkolenia.
// 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) }
Zapisywanie danych o ćwiczeniach i aktywności
Dwa dni później użytkownik rejestruje rzeczywistą sesję ćwiczeń. Zapisz te dane w Health Connect jako sesję ćwiczeń.
W tym przykładzie czas trwania sesji użytkownika dokładnie odpowiadał zaplanowanemu czasowi trwania.
Poniższy fragment kodu może znajdować się w programie obsługi formularzy aplikacji, która rejestruje sesje ćwiczeń w Health Connect. Może się też znajdować w programach do pozyskiwania i eksportowania danych na potrzeby urządzenia do noszenia, które wykrywa i rejestruje sesje ćwiczeń.
insertedPlannedExerciseSessionId jest ponownie użyty z poprzedniego przykładu. W prawdziwej aplikacji identyfikator byłby określany przez użytkownika, który wybiera zaplanowaną sesję ćwiczeń z listy istniejących sesji.
// 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))
Urządzenie śledzące rejestruje też tętno podczas biegu. Poniższy fragment kodu może służyć do generowania rekordów w zakresie docelowym.
W prawdziwej aplikacji główne elementy tego fragmentu kodu mogą znajdować się w procedurze obsługi wiadomości z urządzenia do noszenia, która po zebraniu danych zapisuje pomiary w 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( device = Device(type = Device.Companion.TYPE_WATCH) ) ) val insertedHeartRateRecords = healthConnectClient.insertRecords(listOf(heartRateRecord))
Ocena celów skuteczności
Następnego dnia po treningu użytkownika możesz pobrać zarejestrowane ćwiczenie, sprawdzić, czy są jakieś zaplanowane cele ćwiczeń, i ocenić dodatkowe typy danych, aby określić, czy ustawione cele zostały osiągnięte.
Taki fragment kodu można znaleźć w zadaniu okresowym, które służy do oceny celów skuteczności, lub podczas wczytywania listy ćwiczeń i wyświetlania powiadomienia o celach skuteczności w aplikacji.
// 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 } } } } } } } }
sesje ćwiczeń,
Sesje ćwiczeń mogą obejmować wszystko, od biegania po badmintona.
Zapisywanie sesji ćwiczeń
Oto jak utworzyć żądanie wstawienia, które obejmuje sesję:
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
)
)
}
Odczytywanie sesji ćwiczeń
Oto przykład odczytywania sesji ćwiczeń:
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
}
}
Zapisywanie danych podtypu
Sesje mogą też zawierać opcjonalne dane podtypu, które wzbogacają sesję o dodatkowe informacje.
Na przykład sesje ćwiczeń mogą obejmować zajęcia ExerciseSegment, ExerciseLap i 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()
)