이전 데이터 사용

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 Fit 앱에 보고된 활동당 칼로리와 일치하며, 여기서 각 활동은 자체 칼로리 데이터 버킷을 가져옵니다.

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() 메서드를 사용하여 이전 데이터를 비동기식으로 읽습니다.

다음 예시는 다음에서 DataPoint 인스턴스를 가져오는 방법을 보여줍니다. DataSet:

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개 이 메서드가 기본 계정을 사용하여 호출되고 scopes를 지정합니다. 이는 걸음 수 데이터를 사용할 수 없을 때 권한 패널을 표시합니다(예: 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 내에서 고유한 간격을 정의하는 startTimeendTime가 있어야 합니다.

앱이 기존 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 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")
   
}
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}");
       
}
   
}
}

IntentServiceAndroidManifest.xml 파일에서 선언해야 합니다.