Tworzenie funkcji treningowych za pomocą Health Connect

Jeśli chcesz w swojej aplikacji stworzyć jakość treningu, możesz użyć Health Connect do takich czynności jak:

  • Zapisywanie sesji ćwiczeń
  • Zapisywanie tras treningowych
  • Zapisuj dane treningowe, takie jak tętno, prędkość i dystans
  • Odczytywanie danych o treningach z innych aplikacji

Z tego przewodnika dowiesz się, jak tworzyć te funkcje treningowe. Znajdziesz w nim informacje o typach danych, wykonywaniu w tle, uprawnieniach, zalecanych przepływach pracy i sprawdzonych metodach.

Omówienie: tworzenie kompleksowego trackera treningów

Aby stworzyć kompleksowe rozwiązanie do śledzenia treningów za pomocą Health Connect, wykonaj te podstawowe czynności:

  • prawidłowe wdrażanie uprawnień na podstawie uprawnień związanych ze zdrowiem;
  • Nagrywanie sesji za pomocą ExerciseSessionRecord.
  • zapisywanie danych treningowych w spójny sposób podczas sesji;
  • Prawidłowe zarządzanie wykonywaniem w tle w celu weryfikacji ciągłego zbierania danych.
  • Odczytywanie danych sesji na potrzeby podsumowań i analiz po treningu.

Ten proces umożliwia interoperacyjność z innymi aplikacjami Health Connect i weryfikuje dostęp do danych kontrolowany przez użytkownika.

Zanim zaczniesz

Przed wdrożeniem funkcji treningu:

Kluczowych pojęć

Health Connect reprezentuje dane o treningu za pomocą kilku podstawowych komponentów. ExerciseSessionRecord jest centralnym rekordem treningu,ExerciseSessionRecord zawierającym szczegóły takie jak czas rozpoczęcia i zakończenia oraz rodzaj ćwiczenia. Podczas sesji można rejestrować różne typy danych, np. HeartRateRecord lub SpeedRecord. W przypadku aktywności na zewnątrz ExerciseRoute przechowuje dane GPS, które są powiązane z odpowiednią sesją.

Sesje ćwiczeń

ExerciseSessionRecord to centralny rekord danych treningowych, który reprezentuje pojedynczą sesję treningową. Każdy rekord zawiera:

  • startTime
  • endTime
  • exerciseType
  • Opcjonalne metadane sesji (tytuł, notatki)

ExerciseSessionRecord może też zawierać trasy ćwiczeń, okrążenia i segmenty. Podczas sesji można też rejestrować i powiązywać z nią inne typy danych, np. HeartRateRecord lub SpeedRecord.

Powiązane typy danych

Dane powiązane z sesjami treningowymi są reprezentowane przez poszczególne typy rekordów. Typowe rodzaje:

Pełną listę typów danych znajdziesz w artykule Typy danych w Health Connect.

Trasy ćwiczeń

Możesz powiązać trasę z treningami na świeżym powietrzu za pomocą ExerciseRoute. Trasy składają się z sekwencyjnych obiektów ExerciseRoute.Location, z których każdy zawiera:

  • Długość i szerokość geograficzna
  • Opcjonalna wysokość
  • Opcjonalny namiar
  • Informacje o dokładności
  • Sygnatura czasowa

Łączenie tras sesji

ExerciseRoute zawiera sekwencyjne dane o lokalizacji dotyczące sesji ćwiczeń. Nie jest traktowany jako niezależny rekord w Health Connect. Zamiast tego podczas wstawiania lub aktualizowania ExerciseRoute danych podajesz ExerciseSessionRecord.

Uwagi dotyczące programowania

Aplikacje do śledzenia treningów często muszą działać przez dłuższy czas, zwykle w tle, gdy ekran jest wyłączony. Podczas tworzenia funkcji treningowych ważne jest, aby zastanowić się, jak zarządzać wykonywaniem w tle i poprosić o niezbędne uprawnienia do danych treningowych.

Wykonywanie w tle

Aplikacje do ćwiczeń często działają przy wyłączonym ekranie. W takim przypadku należy używać:

  • Usługi działające na pierwszym planie do pobierania próbek lokalizacji i danych z czujników
  • WorkManager w przypadku odroczonego zapisu lub synchronizacji.
  • Strategie zbiorczego przetwarzania danych w przypadku zwykłego zapisu rekordów

Zachowaj ciągłość, utrzymując spójność identyfikatora sesji we wszystkich operacjach zapisu.

Uprawnienia

Przed odczytaniem lub zapisaniem danych treningu aplikacja musi poprosić o odpowiednie uprawnienia Health Connect. Typowe uprawnienia dotyczące treningów obejmują sesje ćwiczeń, trasy ćwiczeń i dane takie jak tętno czy prędkość. między innymi na następujące działania:

  • Sesje ćwiczeń: uprawnienia do odczytu i zapisu w przypadku ExerciseSessionRecord.
  • Trasy ćwiczeń: uprawnienia do odczytu i zapisu w przypadku ExerciseRoute.
  • Tętno: uprawnienia do odczytu i zapisu w przypadku HeartRateRecord.
  • Szybkość: uprawnienia do odczytu i zapisu w przypadku SpeedRecord.
  • Odległość: uprawnienia do odczytu i zapisu w przypadku DistanceRecord.
  • Kalorie: uprawnienia do odczytu i zapisu dla TotalCaloriesBurnedRecord.
  • Zdobyta wysokość: uprawnienia do odczytu i zapisu dla ElevationGainedRecord.
  • Kadencja kroków: uprawnienia do odczytu i zapisu dla StepsCadenceRecord.
  • Zasilanie: uprawnienia do odczytu i zapisu w przypadku PowerRecord.
  • Czynności: uprawnienia do odczytu i zapisu w przypadku StepsRecord.

Poniższy przykład pokazuje, jak poprosić o wiele uprawnień w przypadku sesji treningowej, która obejmuje dane o trasie, tętnie, odległości, kaloriach, prędkości i krokach:

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.

// Create a set of permissions for required data types
val PERMISSIONS =
    setOf(
  HealthPermission.getReadPermission(ExerciseSessionRecord::class),
  HealthPermission.getWritePermission(ExerciseSessionRecord::class),
  HealthPermission.getReadPermission(ExerciseRoute::class),
  HealthPermission.getWritePermission(ExerciseRoute::class),
  HealthPermission.getReadPermission(HeartRateRecord::class),
  HealthPermission.getWritePermission(HeartRateRecord::class),
  HealthPermission.getReadPermission(SpeedRecord::class),
  HealthPermission.getWritePermission(SpeedRecord::class),
  HealthPermission.getReadPermission(DistanceRecord::class),
  HealthPermission.getWritePermission(DistanceRecord::class),
  HealthPermission.getReadPermission(TotalCaloriesBurnedRecord::class),
  HealthPermission.getWritePermission(TotalCaloriesBurnedRecord::class),
  HealthPermission.getReadPermission(StepsRecord::class),
  HealthPermission.getWritePermission(StepsRecord::class)
)

Użyj 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.

// 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)
  }
}

Użytkownicy mogą w każdej chwili przyznać lub cofnąć uprawnienia, dlatego aplikacja musi sprawdzać uprawnienia za każdym razem przed ich użyciem i obsługiwać sytuacje, w których uprawnienia zostaną utracone.

Aby poprosić o uprawnienia, wywołaj funkcję checkPermissionsAndRun:

if (!granted.containsAll(PERMISSIONS)) {
    requestPermissions.launch(PERMISSIONS)
    // Check if required permissions are not granted, and return
  }
// Permissions already granted; proceed with inserting or reading data

Jeśli musisz poprosić o uprawnienia tylko do jednego typu danych, np. tętna, uwzględnij w zbiorze uprawnień tylko ten typ danych:

Dostęp do danych o tętnie jest chroniony przez te uprawnienia:

  • android.permission.health.READ_HEART_RATE
  • android.permission.health.WRITE_HEART_RATE

Aby dodać do aplikacji funkcję pomiaru tętna, zacznij od poproszenia o uprawnienia do typu danych HeartRateRecord.

Aby móc zapisywać dane o tętnie, musisz zadeklarować to uprawnienie:

<application>
  <uses-permission
android:name="android.permission.health.WRITE_HEART_RATE" />
...
</application>

Aby odczytać dane o tętnie, musisz poprosić o te uprawnienia:

<application>
  <uses-permission
android:name="android.permission.health.READ_HEART_RATE" />
...
</application>

Wdrażanie sesji treningowej

W tej sekcji opisujemy zalecany proces rejestrowania danych treningowych.

Rozpocznij sesję

Aby utworzyć nowy trening:

  1. Wygeneruj unikalny identyfikator sesji: sprawdź, czy ten identyfikator jest stabilny. Jeśli proces aplikacji zostanie zakończony i ponownie uruchomiony, musisz mieć możliwość wznowienia korzystania z tego samego identyfikatora, aby zapobiec fragmentacji sesji.
  2. Ustaw metadata.clientRecordId, aby zapobiec duplikatom podczas ponownych prób synchronizacji.
  3. Wpisz ExerciseSessionRecord: podaj godzinę rozpoczęcia.
  4. Rozpocznij zbieranie danych o typie danych i danych GPS: zacznij je zbierać dopiero po prawidłowym zainicjowaniu rekordu sesji.

Przykład:

val sessionId = UUID.randomUUID().toString()
val sessionClientId = UUID.randomUUID().toString()

val session = ExerciseSessionRecord(
    id = sessionId,
    exerciseType = ExerciseType.EXERCISE_TYPE_RUNNING,
    startTime = Instant.now(),
    endTime = null,
    metadata = Metadata(clientRecordId = sessionClientId),
)

healthConnectClient.insertRecords(listOf(session))

Rejestrowanie tras ćwiczeń

Więcej informacji o wskazówkach dotyczących odczytywania znajdziesz w sekcji Odczytywanie nieprzetworzonych danych.

Podczas rejestrowania trasy ćwiczeń dane należy przesyłać partiami. Oznacza to, że zamiast zapisywać każdy punkt GPS w momencie jego wystąpienia, zbierasz grupę punktów i zapisujesz je wszystkie naraz w ramach jednego wywołania.

Jest to ważne, ponieważ za każdym razem, gdy aplikacja odczytuje lub zapisuje dane w Health Connect, zużywa niewielką ilość baterii i mocy obliczeniowej.

Poniższy kod pokazuje, jak rejestrować dane w partiach:

// 1. Create a list to hold your route locations
val routeLocations = mutableListOf<ExerciseRoute.Location>()

// 2. Add points to your list as the exercise happens
routeLocations.add(
    ExerciseRoute.Location(
        time = Instant.now(),
        latitude = 37.7749,
        longitude = -122.4194
    )
)

// ... keep adding points over a period of time ...

// 3. Save the whole list at once (Batching)
val session = ExerciseSessionRecord(
    startTime = startTime,
    endTime = endTime,
    exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_RUNNING,
    // We pass the whole list here
    exerciseRoute = ExerciseRoute(routeLocations)
)

healthConnectClient.insertRecords(listOf(session))

Kończenie sesji

Po zatrzymaniu zbierania danych:

  • Aktualizacja rekordu: aplikacja aktualizuje ExerciseSessionRecord za pomocą endTime.
  • Kończenie danych: opcjonalnie oblicz wartości podsumowujące (np. łączny dystans lub średnie tempo) i zapisz je jako dodatkowe rekordy.
val finishedSession = session.copy(endTime = Instant.now())
healthConnectClient.updateRecords(listOf(finishedSession))

Odczytywanie danych treningowych

Aplikacje mogą odczytywać sesje ćwiczeń i powiązane z nimi dane, aby podsumowywać aktywność, dostarczać informacji o zdrowiu lub synchronizować dane z serwerem zewnętrznym. Możesz na przykład odczytać ExerciseSessionRecord, a potem wysłać zapytanie o HeartRateRecord lub DistanceRecord, które wystąpiły w tym samym przedziale czasu.

Jeśli chcesz synchronizować dane treningu z serwerem backendu lub aktualizować magazyn danych aplikacji za pomocą Health Connect, użyj ChangeLogs. Umożliwia to pobieranie listy wstawionych, zaktualizowanych lub usuniętych rekordów od określonego momentu, co jest bardziej wydajne niż ręczne śledzenie zmian lub wielokrotne odczytywanie wszystkich danych. Więcej informacji znajdziesz w artykule Synchronizowanie danych z Health Connect.

Odczytywanie sesji

Aby odczytać sesje ćwiczeń, użyj ReadRecordsRequest z typem ExerciseSessionRecord. Zwykle filtrujesz te dane według określonego zakresu czasu.

suspend fun readExerciseSessions(
    healthConnectClient: HealthConnectClient,
    startTime: Instant,
    endTime: Instant
) {
    val response = healthConnectClient.readRecords(
        ReadRecordsRequest(
            recordType = ExerciseSessionRecord::class,
            timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
        )
    )

    for (exerciseRecord in response.records) {
        // Process each session
        val exerciseType = exerciseRecord.exerciseType
        val notes = exerciseRecord.notes
    }
}

Odczytywanie tras

Chociaż ExerciseRoute dane są zapisywane w ramach sesji ćwiczeń, muszą być odczytywane oddzielnie. Użyj metody getExerciseRoute() z identyfikatorem sesji, aby odczytać dane o jej trasie:

suspend fun readExerciseRoute(
    healthConnectClient: HealthConnectClient,
    exerciseSessionRecord: ExerciseSessionRecord
) {
    // Check if the session has a route
    val route = healthConnectClient.getExerciseRoute(
        exerciseSessionRecordId = exerciseSessionRecord.metadata.id
    )

    when (route) {
        is ExerciseRouteResponse.Success -> {
            val locations = route.exerciseRoute.locations
            for (location in locations) {
                // Use latitude, longitude, and altitude
            }
        }
        is ExerciseRouteResponse.NoData -> {
            // Handle case where no route exists
        }
        is ExerciseRouteResponse.ConsentRequired -> {
            // Handle case where permissions are missing
        }
    }
}

Typy danych odczytu

Aby odczytać szczegółowe dane (np. tętno) z sesji, użyj parametrów startTimeendTime, aby odfiltrować żądanie dotyczące tego typu danych.

suspend fun readHeartRateData(
    healthConnectClient: HealthConnectClient,
    exerciseSession: ExerciseSessionRecord
) {
    val response = healthConnectClient.readRecords(
        ReadRecordsRequest(
            recordType = HeartRateRecord::class,
            timeRangeFilter = TimeRangeFilter.between(
                exerciseSession.startTime,
                exerciseSession.endTime
            )
        )
    )

    for (heartRateRecord in response.records) {
        for (sample in heartRateRecord.samples) {
            val bpm = sample.beatsPerMinute
        }
    }
}

Sprawdzone metody

Aby zwiększyć wiarygodność danych i poprawić wygodę użytkowników, postępuj zgodnie z tymi wskazówkami:

  • Częstotliwość zapisu
    • Aktywne śledzenie(na pierwszym planie): w przypadku aktywnych treningów zapisuj dane, gdy staną się dostępne, lub w maksymalnym odstępie 15 minut.
    • Synchronizacja w tle: używaj WorkManager w przypadku odroczonych zapisów. Aby zachować równowagę między danymi w czasie rzeczywistym a wydajnością baterii, ustaw interwał 15-minutowy.
    • Grupowanie: nie zapisuj każdego zdarzenia z czujnika osobno. Dziel żądania na mniejsze części. Health Connect obsługuje do 1000 rekordów w jednym żądaniu zapisu.
  • Utrzymuj stabilne i unikalne identyfikatory sesji: używaj spójnych identyfikatorów sesji. Jeśli sesja zostanie zmodyfikowana lub zaktualizowana, użycie tego samego identyfikatora zapobiegnie traktowaniu jej jako nowego, oddzielnego treningu.
  • Używaj przetwarzania wsadowego w przypadku obu typów danych i punktów trasy: aby zmniejszyć obciążenie wejścia/wyjścia i wydłużyć czas pracy na baterii, grupuj punkty danych w jedno wywołanie insertRecords zamiast zapisywać każdy punkt osobno.
  • Unikaj zapisywania zduplikowanych danych: używaj identyfikatorów klientów. Podczas tworzenia rekordów ustaw wartość metadata.clientRecordId. Health Connect używa tego identyfikatora do identyfikowania unikalnych rekordów. Jeśli spróbujesz zapisać rekord z clientRecordId, który już istnieje, Health Connect zignoruje duplikat lub zaktualizuje istniejący rekord zamiast tworzyć nowy. Ustawienie metadata.clientRecordIdto najskuteczniejszy sposób zapobiegania duplikatom podczas ponownych prób synchronizacji lub ponownej instalacji aplikacji.

    val record = StepsRecord(
        count = 100,
        startTime = startTime,
        endTime = endTime,
        startZoneOffset = ZoneOffset.UTC,
        endZoneOffset = ZoneOffset.UTC,
        metadata = Metadata(
            // Use a unique ID from your own database
            clientRecordId = "daily_steps_2023_10_27_user_123"
        )
    )
    
  • Sprawdź istniejące dane: przed synchronizacją wyślij zapytanie dotyczące zakresu czasu, aby sprawdzić, czy rekordy z Twojej aplikacji już istnieją.

  • Sprawdzanie dokładności GPS-a: przed zapisaniem w ExerciseRoute odfiltruj próbki GPS-a o niskiej dokładności (np. punkty o dużym promieniu dokładności poziomej), aby sprawdzić, czy mapa wygląda przejrzyście i profesjonalnie.

  • Sprawdź, czy znaczniki czasu się nie pokrywają: upewnij się, że nowa sesja nie rozpoczyna się przed zakończeniem poprzedniej. Nakładające się sesje mogą powodować konflikty na panelach aktywności i w obliczeniach podsumowujących.

  • Podaj jasne uzasadnienie uprawnień: użyj przepływu Permission.createIntent, aby wyjaśnić, dlaczego Twoja aplikacja potrzebuje dostępu do danych o zdrowiu, np. „Aby mapować Twoje biegi i obliczać spalone kalorie”.

  • Obsługa wstrzymywania i wznawiania: sprawdź, czy aplikacja prawidłowo obsługuje wstrzymywanie. Gdy użytkownik wstrzyma aktywność, przestań zbierać punkty trasy i typy danych, aby średnie tempo i czas trwania pozostały dokładne.

  • Testuj długotrwałe sesje: monitoruj zużycie baterii podczas sesji trwających kilka godzin, aby sprawdzić, czy interwał grupowania i użycie czujników nie wyczerpują baterii urządzenia.

  • Dopasuj sygnatury czasowe do częstotliwości czujników: dopasuj sygnatury czasowe rekordów do rzeczywistej częstotliwości czujników (np. 1 Hz w przypadku GPS), aby zachować wysoką jakość danych.

Testowanie

Aby sprawdzić poprawność danych i zapewnić wysoką jakość obsługi, postępuj zgodnie z tymi strategiami testowania i zapoznaj się z oficjalną dokumentacją Testowanie najważniejszych przypadków użycia.

Narzędzia do weryfikacji

Lista kontrolna jakości

Typowa architektura

Implementacja treningu zwykle obejmuje:

Komponent Zarządza
Kontroler sesji Stan sesji
Timer
Logika przetwarzania wsadowego
Kontrolery typów danych
Próbkowanie lokalizacji
Warstwa repozytorium (zawiera operacje Health Connect): Wstawianie sesji
Wstawianie typów danych
Wstawianie punktów trasy
Odczytywanie podsumowań sesji
Warstwa interfejsu (wyświetlacze): Czas trwania
Typy danych na żywo
Podgląd mapy
Podział obliczeń
Ślad GPS na żywo

Rozwiązywanie problemów

Krótki opis problemu Możliwa przyczyna Rozdzielczość
Trasa nie jest powiązana z sesją Niezgodność identyfikatora sesji lub zakresu czasu. Sprawdź, czy ExerciseRoute jest zapisany z zakresem czasu, który w całości mieści się w czasie trwania ExerciseSessionRecord. Jeśli później będziesz odwoływać się do sesji, sprawdź, czy używasz spójnych identyfikatorów. Zobacz Nagrywanie tras ćwiczeń.
Brakujące typy danych (np. tętno) Brak uprawnień do zapisu lub nieprawidłowe filtry czasu. Sprawdź, czy masz uprawnienia do określonego typu danych i czy użytkownik je przyznał. Sprawdź, czy ReadRecordsRequest używa TimeRangeFilter, który pasuje do sesji. Zobacz Uprawnienia.
Nie udało się zapisać sesji Pokrywające się sygnatury czasowe. Health Connect może odrzucać rekordy, które pokrywają się z istniejącymi danymi z tej samej aplikacji. Sprawdź, czy startTime nowej sesji następuje po endTime poprzedniej.
Brak zarejestrowanych danych GPS Usługa na pierwszym planie została zamknięta lub jest nieaktywna. Aby zbierać dane przy wyłączonym ekranie, musisz używać usługi pierwszego planu z atrybutem foregroundServiceType="health" lub lokalizacji.
Wyświetlają się zduplikowane rekordy Brak clientRecordId Przypisz unikalny clientRecordIdMetadata każdego rekordu. Dzięki temu Health Connect może usuwać duplikaty, jeśli te same dane zostaną zapisane dwukrotnie podczas ponownej próby synchronizacji. Zobacz sprawdzone metody.

Typowe czynności debugowania

  • Sprawdź stan uprawnień: zawsze wywołuj getPermissionStatus() przed podjęciem próby odczytu lub zapisu. Użytkownicy mogą w dowolnym momencie cofnąć uprawnienia w ustawieniach systemu.
  • Sprawdź tryb wykonywania: jeśli Twoja aplikacja nie zbiera danych w tle, sprawdź, czy w pliku AndroidManifest.xml zadeklarowano prawidłowe uprawnienia i czy użytkownik nie ustawił dla aplikacji trybu „Ograniczenie baterii”.