History API를 사용하면 앱이 피트니스 저장소에서 과거 건강 및 웰빙 데이터를 읽고 삽입하고 업데이트하고 삭제하는 등의 일괄 작업을 실행할 수 있습니다. History API를 사용하여 다음을 수행합니다.
- 다른 앱을 사용하여 삽입되거나 기록된 건강 및 웰빙 데이터를 읽습니다.
- 일괄 데이터를 Google 피트니스로 가져옵니다.
- Google 피트니스의 데이터를 업데이트합니다.
- 앱에서 이전에 저장한 이전 데이터를 삭제합니다.
세션 메타데이터가 포함된 데이터를 삽입하려면 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()
자바
// 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 피트니스 앱에 보고된 활동당 칼로리와 일치하며, 이 경우 각 활동이 자체 칼로리 데이터 버킷을 가져옵니다.
Kotlin
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
인스턴스를 가져오는 방법을 보여줍니다.
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()
자바
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
를 참조하세요.
이 메서드가 기본 계정을 사용하여 호출되고 범위가 지정되지 않은 경우 Google 메서드가 HistoryClient.readDailyTotal()
메서드의 TYPE_STEP_COUNT_DELTA
업데이트를 구독하도록 승인할 필요가 없습니다.
이 기능은 권한 패널을 표시할 수 없는 영역(예: Wear OS 시계 화면)에 사용할 단계 데이터가 필요한 경우에 유용합니다.
사용자는 Google 피트니스 앱, 기타 앱, Wear OS 시계 화면에서 일관되고 신뢰할 수 있는 환경을 제공하므로 일관된 걸음 수를 확인할 수 있습니다. 걸음 수를 일관되게 유지하려면 앱 또는 시계 화면에서 Google 피트니스 플랫폼의 단계를 구독한 후 onExitAmbient()
에서 카운트를 업데이트합니다.
시계 모드에서 이 데이터를 사용하는 방법에 관한 자세한 내용은 시계 화면 정보 표시와 Android 시계 화면 샘플 애플리케이션을 참고하세요.
데이터 삽입
이전 데이터를 삽입하려면 먼저 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()
자바
// 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) }
자바
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
에는 DataPoint
인스턴스 간에 중복 없이 DataSet
내에서 고유한 간격을 정의하는 startTime
와 endTime
가 있어야 합니다.
앱에서 기존 DataPoint
인스턴스와 충돌하는 새 DataPoint
를 삽입하려고 하면 새 DataPoint
가 삭제됩니다. 기존 데이터 포인트와 겹칠 수 있는 새 DataPoint
를 삽입하려면 데이터 업데이트에 설명된 HistoryClient.updateData
메서드를 사용하세요.
그림 1. insertData()
메서드가 기존 DataPoint
와 충돌하는 새 데이터 포인트를 처리하는 방법
데이터 업데이트
Google 피트니스를 사용하면 앱에서 이전에 삽입한 이전 건강 및 웰빙 데이터를 업데이트할 수 있습니다. 새 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()
자바
// 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) }
자바
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
메서드를 사용합니다.
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) }
자바
// 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 피트니스 데이터베이스에 삽입될 때 업데이트를 수신하도록 등록할 수 있습니다. 이러한 데이터 유형의 예로는 키, 가중치, 웨이트 리프팅과 같은 운동이 있습니다. 자세한 내용은 지원되는 데이터 유형의 전체 목록을 참조하세요.
업데이트를 등록하려면 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") }
자바
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}") } } }
자바
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
를 선언해야 합니다.