API истории позволяет вашему приложению выполнять массовые операции в магазине фитнеса: чтение, вставку, обновление и удаление исторических данных о здоровье и самочувствии. Используйте History API, чтобы сделать следующее:
- Читайте данные о здоровье и самочувствии, которые были вставлены или записаны с помощью других приложений.
- Импортируйте пакетные данные в Google Fit.
- Обновите данные в Google Fit.
- Удалите исторические данные, которые ранее хранило ваше приложение.
Чтобы вставить данные, содержащие метаданные сеанса, используйте Sessions API .
Чтение данных
В следующих разделах рассказывается, как читать различные виды агрегатных данных.
Чтение подробных и совокупных данных
Чтобы прочитать исторические данные, создайте экземпляр DataReadRequest
.
Котлин
// 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 bucketBySession allows // bucketing by <a href="/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();
В предыдущем примере используются агрегированные точки данных, где каждая DataPoint
представляет количество шагов, пройденных за день. В этом конкретном случае использования агрегированные точки данных имеют два преимущества:
- Ваше приложение и фитнес-магазин обмениваются меньшими объемами данных.
- Вашему приложению не нужно агрегировать данные вручную.
Агрегированные данные по нескольким типам деятельности
Ваше приложение может использовать запросы данных для получения множества различных типов данных. В следующем примере показано, как создать DataReadRequest
для сжигания калорий за каждое действие, выполненное в указанном диапазоне времени. Полученные данные соответствуют количеству калорий на каждое занятие, указанному в приложении Google Fit, где каждое занятие получает свой собственный блок данных о калориях.
Котлин
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();
После создания экземпляра DataReadRequest
используйте метод HistoryClient.readData()
для асинхронного чтения исторических данных.
В следующем примере показано, как получить экземпляры DataPoint
из 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(); }
Прочитайте ежедневные общие данные
Google Fit также обеспечивает простой доступ к дневной сумме данных определенного типа. Используйте метод HistoryClient.readDailyTotal()
для получения указанного вами типа данных по состоянию на полночь текущего дня в текущем часовом поясе устройства. Например, передайте этому методу тип данных TYPE_STEP_COUNT_DELTA
, чтобы получить общее количество шагов за день. Вы можете передать мгновенный тип данных, который имеет совокупную дневную сумму. Дополнительные сведения о поддерживаемых типах данных см. в разделе DataType.getAggregateType
.
Google Fit не требует авторизации для подписки на обновления TYPE_STEP_COUNT_DELTA
из метода HistoryClient.readDailyTotal()
, если этот метод вызывается с использованием учетной записи по умолчанию и не указаны области действия . Это может быть полезно, если вам нужны данные о шагах для использования в областях, где вы не можете отобразить панель разрешений, например на циферблатах Wear OS.
Пользователи предпочитают видеть единообразное количество шагов в приложении Google Fit, других приложениях и циферблатах Wear OS, поскольку это обеспечивает единообразие и надежность работы. Чтобы поддерживать постоянное количество шагов, подпишитесь на шаги на платформе Google Fit из своего приложения или циферблата, а затем обновите счетчик в onExitAmbient()
. Дополнительные сведения о том, как использовать эти данные в циферблате, см. в разделах «Усложнения циферблата» и «Пример приложения Android WatchFace» .
Вставить данные
Чтобы вставить исторические данные, сначала создайте экземпляр DataSet
:
Котлин
// 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();
После создания экземпляра DataSet
используйте метод HistoryClient.insertData
для асинхронного добавления этих исторических данных.
Котлин
Fitness.getHistoryClient(this, GoogleSignIn.getAccountForExtension(this, fitnessOptions)) .insertData(dataSet) .addOnSuccessListener { Log.i(TAG, "DataSet added successfully!") } .addOnFailureListener { 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!")) .addOnFailureListener(e -> Log.w(TAG, "There was an error adding the DataSet", e)); }
Управляйте конфликтующими точками данных
Каждый DataPoint
в DataSet
вашего приложения должен иметь startTime
и endTime
, которые определяют уникальный интервал внутри этого DataSet
без перекрытия между экземплярами DataPoint
.
Если ваше приложение попытается вставить новый DataPoint
, который конфликтует с существующим экземпляром DataPoint
, новый DataPoint
будет удален. Чтобы вставить новую DataPoint
, которая может перекрываться с существующими точками данных, используйте метод HistoryClient.updateData
, описанный в разделе Обновление данных .
Рисунок 1. Как метод insertData()
обрабатывает новые точки данных, которые конфликтуют с существующим DataPoint
.
Обновить данные
Google Fit позволяет вашему приложению обновлять ранее вставленные исторические данные о здоровье и самочувствии. Чтобы добавить исторические данные для нового DataSet
или добавить новые экземпляры DataPoint
, которые не конфликтуют с существующими точками данных , используйте метод HistoryApi.insertData
.
Чтобы обновить исторические данные, используйте метод HistoryClient.updateData
. Этот метод удаляет все существующие экземпляры DataPoint
, которые перекрываются с экземплярами DataPoint
, добавленными с помощью этого метода.
Чтобы обновить исторические данные о здоровье и самочувствии, сначала создайте экземпляр DataSet
:
Котлин
// 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();
Затем используйте DataUpdateRequest.Builder()
, чтобы создать новый запрос на обновление данных, и используйте метод HistoryClient.updateData
, чтобы выполнить запрос:
Котлин
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!") } .addOnFailureListener { 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!")) .addOnFailureListener(e -> Log.w(TAG, "There was an error updating the DataSet", e));
Удалить данные
Google Fit позволяет вашему приложению удалять ранее вставленные исторические данные о здоровье и самочувствии.
Чтобы удалить исторические данные, используйте метод 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!") } .addOnFailureListener { 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!")) .addOnFailureListener(e -> Log.w(TAG, "There was an error with the deletion request", e));
Приложения могут удалять данные из определенных сеансов или удалять все данные. Дополнительные сведения см. в справочнике по API для DataDeleteRequest
.
Зарегистрируйтесь для получения обновлений данных
Ваше приложение может считывать необработанные данные датчиков в режиме реального времени, зарегистрировавшись в SensorsClient
.
Для других типов данных, которые встречаются реже и подсчитываются вручную, ваше приложение может зарегистрироваться для получения обновлений, когда эти измерения будут вставлены в базу данных Google Fit. Примеры этих типов данных включают рост, вес и тренировки, такие как поднятие тяжестей; подробнее см. полный список поддерживаемых типов данных . Чтобы зарегистрироваться для получения обновлений, используйте HistoryClient.registerDataUpdateListener
.
Следующий фрагмент кода позволяет приложению получать уведомления, когда пользователь вводит новое значение своего веса:
Котлин
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
можно использовать для получения уведомлений об обновлениях:
Котлин
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}"); } } }
IntentService
должен быть объявлен в файле AndroidManifest.xml
.