Interfejs History API pozwala aplikacji wykonywać operacje zbiorcze w sklepie fitness: odczytywanie, wstawianie, aktualizowanie i usuwanie historycznych danych dotyczących zdrowia i samopoczucia. Interfejs History API pozwala:
- Odczytywanie danych o zdrowiu i samopoczuciu wstawionych lub zapisanych za pomocą innych metod aplikacji.
- Importowanie danych zbiorczych do Google Fit.
- Aktualizowanie danych w Google Fit.
- Usuwanie danych historycznych zapisanych wcześniej przez aplikację.
Aby wstawić dane zawierające metadane sesji, użyj funkcji Sessions API.
Odczytywanie danych
W sekcjach poniżej dowiesz się, jak odczytywać różne rodzaje danych zbiorczych.
Odczytywanie danych szczegółowych i zbiorczych
Aby odczytywać dane historyczne, utwórz
DataReadRequest
instancji.
// Read the data that's been collected throughout the past week. val endTime = LocalDateTime.now().atZone(ZoneId.systemDefault()) val startTime = endTime.minusWeeks(1) Log.i(TAG, "Range Start: $startTime") Log.i(TAG, "Range End: $endTime") val readRequest = DataReadRequest.Builder() // The data request can specify multiple data types to return, // effectively combining multiple data queries into one call. // This example demonstrates aggregating only one data type. .aggregate(DataType.AGGREGATE_STEP_COUNT_DELTA) // Analogous to a "Group By" in SQL, defines how data should be // aggregated. // bucketByTime allows for a time span, whereas bucketBySessi<on allows // bucketing by a >href=&qu<ot>;/fit/android/using-sessions"sessions/a. .bucketByTime(1, TimeUnit.DAYS) .setTimeRange(startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS) .build()
// Read the data that's been collected throughout the past week. ZonedDateTime endTime = LocalDateTime.now().atZone(ZoneId.systemDefault()); ZonedDateTime startTime = endTime.minusWeeks(1); Log.i(TAG, "Range Start: $startTime"); Log.i(TAG, "Range End: $endTime"); DataReadRequest readRequest = new DataReadRequest.Builder() // The data request can specify multiple data types to return, // effectively combining multiple data queries into one call. // This example demonstrates aggregating only one data type. .aggregate(DataType.AGGREGATE_STEP_COUNT_DELTA) // Analogous to a "Group By" in SQL, defines how data should be // aggregated. // bucketByTime allows for a time span, while bucketBySession allows // bucketing by sessions. .bucketByTime(1, TimeUnit.DAYS) .setTimeRange(startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS) .build();
W poprzednim przykładzie wykorzystywano zagregowane punkty danych, w których każdy element DataPoint
reprezentuje liczbę kroków przebytych w ciągu dnia. W tym konkretnym przypadku użycia
punkty danych zbiorczych mają 2 zalety:
- Aplikacja i sklep fitness wymieniają mniejsze ilości danych.
- Aplikacja nie musi ręcznie agregować danych.
Dane zbiorcze dla wielu typów aktywności
Aplikacja może korzystać z żądań danych, aby pobierać różne typy danych.
Ten przykład pokazuje, jak utworzyć
DataReadRequest
, aby uzyskać spalone kalorie podczas każdej aktywności
w określonym przedziale czasu. Otrzymane dane odpowiadają kaloriom na aktywność,
w raportach w aplikacji Google Fit, gdzie każda aktywność jest przypisywana do osobnego segmentu.
danych dotyczących kalorii.
val readRequest = DataReadRequest.Builder() .aggregate(DataType.AGGREGATE_CALORIES_EXPENDED) .bucketByActivityType(1, TimeUnit.SECONDS) .setTimeRange(startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS) .build()
DataReadRequest readRequest = new DataReadRequest.Builder() .aggregate(DataType.AGGREGATE_CALORIES_EXPENDED) .bucketByActivityType(1, TimeUnit.SECONDS) .setTimeRange(startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS) .build();
Po utworzeniu instancji DataReadRequest
skorzystaj z metody
HistoryClient.readData()
.
do asynchronicznego odczytu danych historycznych.
Ten przykład pokazuje, jak uzyskać instancje DataPoint
z
DataSet
:
Fitness.getHistoryClient(this, GoogleSignIn.getAccountForExtension(this, fitnessOptions)) .readData(readRequest) .addOnSuccessListener { response -> // The aggregate query puts datasets into buckets, so flatten into a // single list of datasets for (dataSet in response.buckets.flatMap { it.dataSets }) { dumpDataSet(dataSet) } } .addOnFailureListener { e -> Log.w(TAG,"There was an error reading data from Google Fit", e) } fun dumpDataSet(dataSet: DataSet) { Log.i(TAG, "Data returned for Data type: ${dataSet.dataType.name}") for (dp in dataSet.dataPoints) { Log.i(TAG,"Data point:") Log.i(TAG,"\tType: ${dp.dataType.name}") Log.i(TAG,"\tStart: ${dp.getStartTimeString()}") Log.i(TAG,"\tEnd: ${dp.getEndTimeString()}") for (field in dp.dataType.fields) { Log.i(TAG,"\tField: ${field.name.toString()} Value: ${dp.getValue(field)}") } } } fun DataPoint.getStartTimeString() = Instant.ofEpochSecond(this.getStartTime(TimeUnit.SECONDS)) .atZone(ZoneId.systemDefault()) .toLocalDateTime().toString() fun DataPoint.getEndTimeString() = Instant.ofEpochSecond(this.getEndTime(TimeUnit.SECONDS)) .atZone(ZoneId.systemDefault()) .toLocalDateTime().toString()
Fitness.getHistoryClient(this, GoogleSignIn.getAccountForExtension(this, fitnessOptions)) .readData(readRequest) .addOnSuccessListener (response -> { // The aggregate query puts datasets into buckets, so convert to a // single list of datasets for (Bucket bucket : response.getBuckets()) { for (DataSet dataSet : bucket.getDataSets()) { dumpDataSet(dataSet); } } }) .addOnFailureListener(e -> Log.w(TAG, "There was an error reading data from Google Fit", e)); } private void dumpDataSet(DataSet dataSet) { Log.i(TAG, "Data returned for Data type: ${dataSet.dataType.name}"); for (DataPoint dp : dataSet.getDataPoints()) { Log.i(TAG,"Data point:"); Log.i(TAG,"\tType: ${dp.dataType.name}"); Log.i(TAG,"\tStart: ${dp.getStartTimeString()}"); Log.i(TAG,"\tEnd: ${dp.getEndTimeString()}"); for (Field field : dp.getDataType().getFields()) { Log.i(TAG,"\tField: ${field.name.toString()} Value: ${dp.getValue(field)}"); } } } private String getStartTimeString() { return Instant.ofEpochSecond(this.getStartTime(TimeUnit.SECONDS)) .atZone(ZoneId.systemDefault()) .toLocalDateTime().toString(); } private String getEndTimeString() { return Instant.ofEpochSecond(this.getEndTime(TimeUnit.SECONDS)) .atZone(ZoneId.systemDefault()) .toLocalDateTime().toString(); }
Odczytaj łączne dane dzienne
Google Fit zapewnia też prosty dostęp do dziennej łącznej
określonego typu danych. Użyj
HistoryClient.readDailyTotal()
metody pobierania określonego typu danych, począwszy od północy bieżącego
dzień w bieżącej strefie czasowej urządzenia. Na przykład przekaż parametr
TYPE_STEP_COUNT_DELTA
typ danych do tej metody, aby pobrać sumę dzienną
kroków. Możesz przekazywać natychmiastowy typ danych, który zawiera zbiorcze dzienne dane
Łącznie. Więcej informacji na temat obsługiwanych typów danych znajdziesz w artykule
DataType.getAggregateType
Subskrybowanie Google Fit nie wymaga autoryzacji
Aktualizacje aplikacji HistoryClient.readDailyTotal()
(TYPE_STEP_COUNT_DELTA
)
w przypadku, gdy ta metoda jest wywoływana z użyciem konta domyślnego, a nie
i zakresy.
Może to być przydatne, jeśli potrzebujesz danych o krokach do użytku na obszarach, na których nie możesz
aby wyświetlić panel uprawnień, na przykład na tarczach zegarka z Wear OS.
Użytkownicy wolą widzieć spójną liczbę kroków w aplikacji Google Fit,
i tarcze zegarka z Wear OS, ponieważ mają one dostęp
spójne i niezawodne usługi. Aby liczba kroków była spójna, zasubskrybuj:
kroki na platformie Google Fit w aplikacji lub na tarczy zegarka,
Zaktualizuj licznik w
onExitAmbient()
Więcej informacji o korzystaniu z tych danych na tarczy zegarka:
Widżety tarczy zegarka
oraz przykładowa aplikacja Android WatchFace.
Wstaw dane
Aby wstawić dane historyczne, najpierw utwórz DataSet
instancję:
// Declare that the data being inserted was collected during the past hour. val endTime = LocalDateTime.now().atZone(ZoneId.systemDefault()) val startTime = endTime.minusHours(1) // Create a data source val dataSource = DataSource.Builder() .setAppPackageName(this) .setDataType(DataType.TYPE_STEP_COUNT_DELTA) .setStreamName("$TAG - step count") .setType(DataSource.TYPE_RAW) .build() // For each data point, specify a start time, end time, and the // data value -- in this case, 950 new steps. val stepCountDelta = 950 val dataPoint = DataPoint.builder(dataSource) .setField(Field.FIELD_STEPS, stepCountDelta) .setTimeInterval(startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS) .build() val dataSet = DataSet.builder(dataSource) .add(dataPoint) .build()
// Declare that the data being inserted was collected during the past hour. ZonedDateTime endTime = LocalDateTime.now().atZone(ZoneId.systemDefault()); ZonedDateTime startTime = endTime.minusHours(1); // Create a data source DataSource dataSource = new DataSource.Builder() .setAppPackageName(this) .setDataType(DataType.TYPE_STEP_COUNT_DELTA) .setStreamName("$TAG - step count") .setType(DataSource.TYPE_RAW) .build(); // For each data point, specify a start time, end time, and the // data value -- in this case, 950 new steps. int stepCountDelta = 950; DataPoint dataPoint = DataPoint.builder(dataSource) .setField(Field.FIELD_STEPS, stepCountDelta) .setTimeInterval(startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS) .build(); DataSet dataSet = DataSet.builder(dataSource) .add(dataPoint) .build();
Po utworzeniu instancji DataSet
skorzystaj z metody
HistoryClient.insertData
.
asynchronicznie do dodawania tych danych historycznych.
Fitness.getHistoryClient(this, GoogleSignIn.getAccountForExtension(this, fitnessOptions)) .insertData(dataSet) .addOnSuccessListener { Log.i(TAG, "DataSet added successfully!") } .addOnFailureList>ener { e - Log.w(TAG, "There was an error adding the DataSet", e) }
Fitness.getHistoryClient(this, GoogleSignIn.getAccountForExtension(this, fitnessOptions)) .insertData(dataSet) .addOnSuccessListener (unused -> Log.i(TAG, "DataSet added successfully!")) .addOnFailureLi>stener(e - Log.w(TAG, "There was an error adding the DataSet", e)); }
Zarządzanie sprzecznymi punktami danych
Każdy
DataPoint
w elemencie DataSet
aplikacji musisz mieć atrybuty startTime
i endTime
, które definiują
unikalny przedział czasu w wybranym okresie (DataSet
) bez nakładania się między DataPoint
instancji.
Jeśli aplikacja próbuje wstawić nowy DataPoint
, który koliduje z dotychczasowym
DataPoint
, nowa instancja DataPoint
została odrzucona. Aby wstawić nowy
DataPoint
, które mogą pokrywać się z istniejącymi punktami danych, użyj
HistoryClient.updateData
.
opisane w artykule Aktualizowanie danych.
Rysunek 1. Jak metoda insertData()
obsługuje nowe punkty danych, które
koliduje z istniejącym elementem DataPoint
.
Zaktualizuj dane
Dzięki Google Fit aplikacja może aktualizować historyczne dane o zdrowiu i samopoczuciu,
wcześniej wstawione. Aby dodać dane historyczne do nowego elementu (DataSet
) lub nowy
DataPoint
instancji, które nie kolidują z istniejącymi danymi
punktów, użyj metody HistoryApi.insertData
.
Aby zaktualizować dane historyczne, użyj metody HistoryClient.updateData
. Ten
usuwa wszystkie istniejące instancje DataPoint
, które nakładają się na DataPoint
do instancji dodanych przy użyciu tej metody.
Aby zaktualizować historyczne dane o zdrowiu i samopoczuciu, najpierw utwórz DataSet
instancja:
// Declare that the historical data was collected during the past 50 minutes. val endTime = LocalDateTime.now().atZone(ZoneId.systemDefault()) val startTime = endTime.minusMinutes(50) // Create a data source val dataSource = DataSource.Builder() .setAppPackageName(this) .setDataType(DataType.TYPE_STEP_COUNT_DELTA) .setStreamName("$TAG - step count") .setType(DataSource.TYPE_RAW) .build() // Create a data set // For each data point, specify a start time, end time, and the // data value -- in this case, 1000 new steps. val stepCountDelta = 1000 val dataPoint = DataPoint.builder(dataSource) .setTimeInterval(startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS) .setField(Field.FIELD_STEPS, stepCountDelta) .build() val dataSet = DataSet.builder(dataSource) .add(dataPoint) .build()
// Declare that the historical data was collected during the past 50 minutes. ZonedDateTime endTime = LocalDateTime.now().atZone(ZoneId.systemDefault()); ZonedDateTime startTime = endTime.minusMinutes(50); // Create a data source DataSource dataSource = new DataSource.Builder() .setAppPackageName(this) .setDataType(DataType.TYPE_STEP_COUNT_DELTA) .setStreamName("$TAG - step count") .setType(DataSource.TYPE_RAW) .build(); // Create a data set // For each data point, specify a start time, end time, and the // data value -- in this case, 1000 new steps. int stepCountDelta = 1000; DataPoint dataPoint = DataPoint.builder(dataSource) .setTimeInterval(startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS) .setField(Field.FIELD_STEPS, stepCountDelta) .build(); DataSet dataSet = DataSet.builder(dataSource) .add(dataPoint) .build();
Następnie użyj narzędzia DataUpdateRequest.Builder()
, aby utworzyć nową prośbę o aktualizację danych.
użyj metody HistoryClient.updateData
, aby wysłać żądanie:
val request = DataUpdateRequest.Builder() .setDataSet(dataSet) .setTimeInterval(startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS) .build() Fitness.getHistoryClient(this, GoogleSignIn.getAccountForExtension(this, fitnessOptions)) .updateData(request) .addOnSuccessListener { Log.i(TAG, "DataSet updated successfully!") } .addOnFailureList>ener { e - Log.w(TAG, "There was an error updating the DataSet", e) }
DataUpdateRequest request = new DataUpdateRequest.Builder() .setDataSet(dataSet) .setTimeInterval(startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS) .build(); Fitness.getHistoryClient(this, GoogleSignIn.getAccountForExtension(this, fitnessOptions)) .updateData(request) .addOnSuccessListener(unused -> Log.i(TAG, "DataSet updated successfully!")) .addOnFailureLi>stener(e - Log.w(TAG, "There was an error updating the DataSet", e));
Usuń dane
Google Fit umożliwia aplikacji usuwanie historycznych danych o zdrowiu i samopoczuciu, wcześniej wstawione.
Aby usunąć dane historyczne, użyj funkcji
HistoryClient.deleteData
:
// Declare that this code deletes step count information that was collected // throughout the past day. val endTime = LocalDateTime.now().atZone(ZoneId.systemDefault()) val startTime = endTime.minusDays(1) // Create a delete request object, providing a data type and a time interval val request = DataDeleteRequest.Builder() .setTimeInterval(startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS) .addDataType(DataType.TYPE_STEP_COUNT_DELTA) .build() // Invoke the History API with the HistoryClient object and delete request, and // then specify a callback that will check the result. Fitness.getHistoryClient(this, GoogleSignIn.getAccountForExtension(this, fitnessOptions)) .deleteData(request) .addOnSuccessListener { Log.i(TAG, "Data deleted successfully!") } .addOnFailureList>ener { e - Log.w(TAG, "There was an error with the deletion request", e) }
// Declare that this code deletes step count information that was collected // throughout the past day. ZonedDateTime endTime = LocalDateTime.now().atZone(ZoneId.systemDefault()); ZonedDateTime startTime = endTime.minusDays(1); // Create a delete request object, providing a data type and a time interval DataDeleteRequest request = new DataDeleteRequest.Builder() .setTimeInterval(startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS) .addDataType(DataType.TYPE_STEP_COUNT_DELTA) .build(); // Invoke the History API with the HistoryClient object and delete request, and // then specify a callback that will check the result. Fitness.getHistoryClient(this, GoogleSignIn.getAccountForExtension(this, fitnessOptions)) .deleteData(request) .addOnSuccessListener (unused -> Log.i(TAG, "Data deleted successfully!")) .addOnFailureLi>stener(e - Log.w(TAG, "There was an error with the deletion request", e));
Aplikacje mogą usuwać dane z określonych sesji lub
Usuń wszystkie dane. Więcej informacji znajdziesz w dokumentacji interfejsu API w sekcji
DataDeleteRequest
Zarejestruj się, aby otrzymywać aktualizacje danych
Aplikacja może odczytywać nieprzetworzone dane z czujnika w czasie rzeczywistym, rejestrując się w
SensorsClient
W przypadku innych rodzajów danych, które są rzadziej liczone i są liczone ręcznie,
aplikacja może się zarejestrować, aby otrzymywać aktualizacje po wstawieniu tych pomiarów
w bazie danych Google Fit. Mogą to być na przykład wysokość,
treningi takie jak podnoszenie ciężarów, więcej szczegółów znajdziesz na pełnej liście
obsługiwanych typach danych.
Aby zarejestrować się w celu otrzymywania aktualizacji, użyj
HistoryClient.registerDataUpdateListener
Ten fragment kodu pozwala aplikacji powiadamiać Cię, gdy użytkownik wpisze dla ich wagi:
val intent = Intent(this, MyDataUpdateService::class.java) val pendingIntent = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) val request = DataUpdateListenerRegistrationRequest.Builder() .setDataType(DataType.TYPE_WEIGHT) .setPendingIntent(pendingIntent) .build() Fitness.getHistoryClient(this, GoogleSignIn.getAccountForExtension(this, fitnessOptions)) .registerDataUpdateListener(request) .addOnSuccessListener { Log.i(TAG, "DataUpdateListener registered") }
Intent intent = new Intent(this, MyDataUpdateService.class); PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) DataUpdateListenerRegistrationRequest request = new DataUpdateListenerRegistrationRequest.Builder() .setDataType(DataType.TYPE_WEIGHT) .setPendingIntent(pendingIntent) .build(); Fitness.getHistoryClient(this, GoogleSignIn.getAccountForExtension(this, fitnessOptions)) .registerDataUpdateListener(request) .addOnSuccessListener(unused -> Log.i(TAG, "DataUpdateListener registered"));
IntentService
może służyć do otrzymywania powiadomień o aktualizacjach:
class MyDataUpdateService : IntentService("MyDataUpdateService") { override fun onHandleIntent(intent: Intent?) { val update = DataUpdateNotification.getDataUpdateNotification(intent) // Show the time interval over which the data points were collected. // To extract specific data values, in this case the user's weight, // use DataReadRequest. update?.apply { val start = getUpdateStartTime(TimeUnit.MILLISECONDS) val end = getUpdateEndTime(TimeUnit.MILLISECONDS) Log.i(TAG, "Data Update start: $start end: $end DataType: ${dataType.name}") } } }
public class MyDataUpdateService extends IntentService { public MyDataUpdateService(String name) { super("MyDataUpdateService"); } @Override protected void onHandleIntent(@Nullable Intent intent) { if (intent != null) { DataUpdateNotification update = DataUpdateNotification.getDataUpdateNotification(intent); // Show the time interval over which the data points // were collected. // To extract specific data values, in this case the user's weight, // use DataReadRequest. if (update != null) { long start = update.getUpdateStartTime(TimeUnit.MILLISECONDS); long end = update.getUpdateEndTime(TimeUnit.MILLISECONDS); } Log.i(TAG, "Data Update start: $start end: $end DataType: ${dataType.name}"); } } }
W pliku AndroidManifest.xml
musi być zadeklarowana właściwość IntentService
.