使用历史数据

借助 History API,您的应用可以对健身存储区执行批量操作: 读取、插入、更新和删除历史健康和健身数据。 使用 History API 执行以下操作:

  • 读取使用其他应用插入或记录的健康和健身数据。
  • 将批量数据导入 Google 健身。
  • 更新 Google 健身中的数据。
  • 删除应用之前存储的历史数据。

如需插入包含会话元数据的数据,请使用 Sessions API

读取数据

以下各部分介绍了如何读取不同类型的汇总数据。

读取详细的汇总数据

要读取历史数据,请创建一个 DataReadRequest 实例。

KotlinJava
// 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 健身应用中报告,其中每项运动都有自己的分桶 热量数据

KotlinJava
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() 方法异步读取历史数据。

以下示例演示了如何从 DataSet 获取 DataPoint 实例:

KotlinJava
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 健身还提供简单的方式来访问指定数据类型的每日总量。使用 HistoryClient.readDailyTotal() 方法检索您指定的数据类型(截至当前时间的午夜) 日期(以设备当前时区为准)。例如,将 TYPE_STEP_COUNT_DELTA 数据类型添加到此方法,以检索每日总计 步骤。您可以传入包含每日汇总总和的瞬时数据类型。如需详细了解支持的数据类型,请参阅 DataType.getAggregateType

如果使用默认账号调用 HistoryClient.readDailyTotal() 方法且未指定任何镜重,Google Fit 无需授权即可通过 HistoryClient.readDailyTotal() 方法订阅 TYPE_STEP_COUNT_DELTA 更新。如果您需要在无法检测的地区使用步数数据,那么这项设置将非常有用 以显示权限面板,例如在 Wear OS 表盘上显示权限面板。

用户希望 Google 健身应用中显示的步数是一致的, 以及 Wear OS 表盘主题 始终如一、可靠的体验为保持步数的一致性,请订阅 Google 健身平台中的步骤,然后 更新计数 onExitAmbient()。 如需详细了解如何在表盘中使用这些数据,请参阅 表盘复杂功能Android WatchFace 示例应用

插入数据

如需插入历史数据,请先创建一个 DataSet 实例:

KotlinJava
// 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 方法异步添加此历史数据。

KotlinJava
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 中,必须具有 startTimeendTime,后者用于定义 该DataSet内的唯一区间,DataPoint之间没有重叠 实例。

如果您的应用尝试插入与现有 DataPoint 实例冲突的新 DataPoint,系统会舍弃新 DataPoint。如需插入可能与现有数据点重叠的新 DataPoint,请使用更新数据中所述的 HistoryClient.updateData 方法。

如果数据点的时长与任何现有数据点重叠,则无法插入该数据点

图 1. insertData() 方法如何处理与现有 DataPoint 冲突的新数据点。

更新数据

借助 Google Fit,您的应用可以更新之前插入的历史健康数据。如需为新的 DataSet 添加历史数据,或添加不会与现有数据点冲突的新 DataPoint 实例,请使用 HistoryApi.insertData 方法。

如需更新历史数据,请使用 HistoryClient.updateData 方法。此方法会删除与使用此方法添加的 DataPoint 实例重叠的所有现有 DataPoint 实例。

如需更新历史健康数据,请先创建一个 DataSet 实例:

KotlinJava
// 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 方法发出请求:

KotlinJava
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 健身,您的应用可以删除之前插入的历史健康和健身数据。

如需删除历史数据,请使用 HistoryClient.deleteData 方法:

KotlinJava
// 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));

应用可以删除特定会话中的数据,也可以删除所有数据。如需了解详情,请参阅 DataDeleteRequest 的 API 参考文档。

注册接收数据更新

您的应用可以通过向 SensorsClient

对于频率较低且手动统计的其他类型的数据,您的应用可以注册以在这些测量结果插入 Google Fit 数据库时接收更新。此类数据类型的示例包括身高、体重和举重等锻炼;如需了解详情,请参阅支持的数据类型的完整列表。如需注册以接收更新,请使用 HistoryClient.registerDataUpdateListener

以下代码段可让应用在用户输入新的 权重值:

KotlinJava
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 可用于接收更新通知:

KotlinJava
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}");
       
}
   
}
}

必须在 AndroidManifest.xml 文件中声明 IntentService