歷史紀錄 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 就不需要授權,即可從 HistoryClient.readDailyTotal()
方法訂閱 TYPE_STEP_COUNT_DELTA
更新。如果您需要在無法使用的某些區域使用步數資料,這項功能就能派上用場
,例如在 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));
}
管理相衝突的資料點
應用程式 DataSet
中的每個 DataPoint
都必須有 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));
應用程式可以刪除特定工作階段的資料,或刪除所有資料。詳情請參閱 DataDeleteRequest
的 API 參考資料。
註冊資料更新
您的應用程式可以註冊
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
檔案中宣告。