History API ช่วยให้แอปดำเนินการแบบกลุ่มใน Store การออกกำลังกายได้ ทั้งการอ่าน แทรก อัปเดต และลบข้อมูลประวัติด้านสุขภาพและความแข็งแรงสมบูรณ์ ใช้ 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
แต่ละรายการแสดงถึงจำนวนก้าวที่เดินในแต่ละวัน สำหรับกรณีการใช้งานนี้
จุดข้อมูลรวมมีข้อดี 2 ประการ ได้แก่
- แอปและร้านค้าฟิตเนสจะแลกเปลี่ยนข้อมูลปริมาณน้อยกว่า
- แอปของคุณไม่จําเป็นต้องรวบรวมข้อมูลด้วยตนเอง
รวมข้อมูลสำหรับกิจกรรมหลายประเภท
แอปของคุณสามารถใช้คําขอข้อมูลเพื่อดึงข้อมูลประเภทต่างๆ ได้ ตัวอย่างต่อไปนี้แสดงวิธีสร้าง 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()
เพื่ออ่านข้อมูลย้อนหลังแบบไม่พร้อมกัน
ตัวอย่างต่อไปนี้แสดงวิธีรับอินสแตนซ์ 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()
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
Google Fit ไม่บังคับให้สมัครใช้บริการอัปเดต TYPE_STEP_COUNT_DELTA
จากเมธอด HistoryClient.readDailyTotal()
เมื่อมีการเรียกใช้เมธอดนี้โดยใช้บัญชีเริ่มต้นและไม่ได้ระบุขอบเขต
ซึ่งจะเป็นประโยชน์หากคุณต้องใช้ข้อมูลขั้นตอนสำหรับการใช้งานในพื้นที่ที่คุณแสดงแผงสิทธิ์ไม่ได้ เช่น บนหน้าปัด 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)); }
จัดการจุดข้อมูลที่ขัดแย้งกัน
แต่ละรายการ
DataPoint
ใน DataSet
ของแอปต้องมี 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));
แอปจะลบข้อมูลออกจากเซสชันที่เจาะจงหรือลบข้อมูลทั้งหมดก็ได้ ดูข้อมูลเพิ่มเติมได้ในเอกสารอ้างอิง API สำหรับ DataDeleteRequest
ลงทะเบียนอัปเดตข้อมูล
แอปอ่านข้อมูลดิบของเซ็นเซอร์ได้แบบเรียลไทม์โดยลงทะเบียนกับ 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}"); } } }
ต้องประกาศ IntentService
ในไฟล์ AndroidManifest.xml