透過 History API,應用程式可以在健身商店中執行大量作業:讀取、插入、更新及刪除歷來健康與保健資料。使用 History API 執行下列操作:
- 讀取透過其他應用程式插入或記錄的健康與保健資料。
- 將批次資料匯入 Google Fit。
- 更新 Google Fit 的資料。
- 刪除應用程式先前儲存的歷來資料。
如要插入包含工作階段中繼資料的資料,請使用 Sessions API。
讀取資料
以下章節將說明如何讀取不同類型的匯總資料。
讀取詳細的匯總資料
如要讀取歷來資料,請建立 DataReadRequest
執行個體。
Kotlin
// 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()
Java
// 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 應用程式記錄的卡路里相符,其中每項活動都會取得專屬的卡路里資料值區。
Kotlin
val readRequest = DataReadRequest.Builder() .aggregate(DataType.AGGREGATE_CALORIES_EXPENDED) .bucketByActivityType(1, TimeUnit.SECONDS) .setTimeRange(startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS) .build()
Java
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
例項:
Kotlin
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()
Java
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
。
使用預設帳戶呼叫 TYPE_STEP_COUNT_DELTA
更新,且未指定範圍時,Google Fit 不需要授權,就能訂閱 HistoryClient.readDailyTotal()
方法的 TYPE_STEP_COUNT_DELTA
更新。如果需要步數資料才能在無法顯示權限面板的區域 (例如 Wear OS 錶面) 使用,這個方法就能派上用場。
使用者偏好在 Google Fit 應用程式、其他應用程式和 Wear OS 錶面中看到一致的步數,因為這樣可以提供一致且可靠的使用體驗。如要保持步數一致,請在 Google Fit 平台上訂閱應用程式或錶面,然後更新 onExitAmbient()
中的步數。如要進一步瞭解如何在錶面中使用這項資料,請參閱「錶面小工具」和「Android WatchFace 範例應用程式」。
插入資料
如要插入歷來資料,請先建立DataSet
執行個體:
Kotlin
// 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()
Java
// 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
方法,以非同步的方式加入這些歷來資料。
Kotlin
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) }
Java
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
執行個體:
Kotlin
// 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()
Java
// 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
方法提出要求:
Kotlin
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) }
Java
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
方法:
Kotlin
// 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) }
Java
// 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
。
下列程式碼片段可讓應用程式在使用者輸入新的體重值時收到通知:
Kotlin
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") }
Java
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
可用於接收更新通知:
Kotlin
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}") } } }
Java
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
。