ซิงค์ข้อมูล

คู่มือนี้ใช้ได้กับ Health Connect เวอร์ชัน 1.1.0-alpha12

แอปส่วนใหญ่ที่ผสานรวมกับ Health Connect จะมีที่เก็บข้อมูลของตัวเองซึ่ง ทำหน้าที่เป็นแหล่งข้อมูลที่เชื่อถือได้ Health Connect มีวิธีช่วยให้แอป ซิงค์กันอยู่เสมอ

กระบวนการซิงค์อาจเกี่ยวข้องกับการดำเนินการต่อไปนี้บางส่วนหรือทั้งหมด ทั้งนี้ขึ้นอยู่กับสถาปัตยกรรมของแอป

  • ป้อนข้อมูลใหม่หรือข้อมูลที่อัปเดตจากที่เก็บข้อมูลของแอปไปยัง Health Connect
  • ดึงการเปลี่ยนแปลงข้อมูลจาก Health Connect ไปยังที่เก็บข้อมูลของแอป
  • ลบข้อมูลออกจาก Health Connect เมื่อมีการลบข้อมูลในที่เก็บข้อมูลของแอป

ในแต่ละกรณี ให้ตรวจสอบว่ากระบวนการซิงค์ทำให้ Health Connect และที่เก็บข้อมูลของแอปสอดคล้องกัน

ป้อนข้อมูลไปยัง Health Connect

ส่วนแรกของกระบวนการซิงค์คือการป้อนข้อมูลจากที่เก็บข้อมูลของแอป ไปยังที่เก็บข้อมูลของ Health Connect

เตรียมข้อมูล

โดยปกติแล้ว บันทึกใน Datastore ของแอปจะมีรายละเอียดต่อไปนี้

  • คีย์ที่ไม่ซ้ำกัน เช่น UUID
  • เวอร์ชันหรือการประทับเวลา

เมื่อซิงค์ข้อมูลกับ Health Connect ให้ระบุและป้อนเฉพาะข้อมูลที่ แทรก อัปเดต หรือลบตั้งแต่การซิงค์ครั้งล่าสุด

เขียนข้อมูลไปยัง Health Connect

หากต้องการป้อนข้อมูลลงใน Health Connect ให้ทำตามขั้นตอนต่อไปนี้

  1. รับรายการข้อมูลใหม่ อัปเดต หรือลบจาก Datastore ของแอป
  2. สำหรับแต่ละรายการ ให้สร้างออบเจ็กต์ Record ที่เหมาะกับประเภทข้อมูลนั้น เช่น สร้างWeightRecordออบเจ็กต์สำหรับข้อมูลที่เกี่ยวข้องกับน้ำหนัก
  3. ระบุออบเจ็กต์ Metadata ด้วย Record แต่ละรายการ ซึ่งรวมถึง clientRecordId ซึ่งเป็นรหัสจาก Datastore ของแอปที่คุณใช้ เพื่อระบุระเบียนที่ไม่ซ้ำกันได้ คุณใช้คีย์ที่ไม่ซ้ำกันที่มีอยู่สำหรับ การดำเนินการนี้ได้ หากข้อมูลของคุณมีการควบคุมเวอร์ชัน ให้ระบุ clientRecordVersionที่สอดคล้องกับการควบคุมเวอร์ชันที่ใช้ในข้อมูลด้วย หากไม่ได้กำหนดเวอร์ชัน คุณสามารถใช้Longค่า ของการประทับเวลาปัจจุบันแทนได้

    val recordVersion = 0L
    // Specify as needed
    // The clientRecordId is an ID that you choose for your record. This
    // is often the same ID you use in your app's datastore.
    val clientRecordId = "<your-record-id>"
    
    val record = WeightRecord(
        metadata = Metadata.activelyRecorded(
            clientRecordId = clientRecordId,
            clientRecordVersion = recordVersion,
            device = Device(type = Device.TYPE_SCALE)
        ),
        weight = Mass.kilograms(62.0),
        time = Instant.now(),
        zoneOffset = ZoneOffset.UTC,
    )
    healthConnectClient.insertRecords(listOf()(record))
    
    
  4. Upsert ข้อมูลไปยัง Health Connect โดยใช้ insertRecords การแทรก/อัปเดตข้อมูลหมายความว่าข้อมูลที่มีอยู่ ใน Health Connect จะถูกเขียนทับตราบใดที่ค่าclientRecordId อยู่ในที่เก็บข้อมูลของ Health Connect และclientRecordVersion มีค่าสูงกว่าค่าที่มีอยู่ ไม่เช่นนั้น ระบบจะเขียนข้อมูลที่แทรก/อัปเดต เป็นข้อมูลใหม่

    healthConnectClient.insertRecords(arrayListOf(record))
    

ดูข้อควรพิจารณาในทางปฏิบัติสำหรับการป้อนข้อมูลได้ที่แนวทางปฏิบัติแนะนำสำหรับเขียนข้อมูล

จัดเก็บรหัส Health Connect

หากแอปของคุณอ่านข้อมูลจาก Health Connect ด้วย ให้จัดเก็บ id ของ Health Connect สำหรับบันทึกหลังจากที่คุณแทรก/อัปเดต คุณต้องใช้ id นี้เพื่อประมวลผลการลบเมื่อ ดึงการเปลี่ยนแปลงข้อมูลจาก Health Connect

ฟังก์ชัน insertRecords จะแสดงผล InsertRecordsResponse ที่มีรายการค่า id ใช้การตอบกลับเพื่อรับรหัสระเบียนและจัดเก็บ

val response = healthConnectClient.insertRecords(arrayListOf(record))

for (recordId in response.recordIdsList) {
    // Store recordId to your app's datastore
}

ดึงข้อมูลจาก Health Connect

ส่วนที่ 2 ของกระบวนการซิงค์คือการดึงข้อมูลการเปลี่ยนแปลงจาก Health Connect ไปยังที่เก็บข้อมูลของแอป การเปลี่ยนแปลงข้อมูลอาจรวมถึงการอัปเดตและการลบ

รับโทเค็นการเปลี่ยนแปลง

หากต้องการดูรายการการเปลี่ยนแปลงที่จะดึงจาก Health Connect แอปของคุณต้องติดตามโทเค็นการเปลี่ยนแปลง คุณสามารถใช้โทเค็นเหล่านี้เมื่อขอการเปลี่ยนแปลงเพื่อรับทั้งรายการการเปลี่ยนแปลงข้อมูลและโทเค็นการเปลี่ยนแปลงใหม่ที่จะใช้ในครั้งถัดไป

หากต้องการรับโทเค็นการเปลี่ยนแปลง ให้เรียกใช้ getChangesToken และ ระบุประเภทข้อมูลที่จำเป็น

val changesToken = healthConnectClient.getChangesToken(
    ChangesTokenRequest(recordTypes = setOf(WeightRecord::class))
)

ตรวจสอบการเปลี่ยนแปลงข้อมูล

ตอนนี้คุณได้รับโทเค็นการเปลี่ยนแปลงแล้ว ให้ใช้โทเค็นดังกล่าวเพื่อรับการเปลี่ยนแปลงทั้งหมด เราขอแนะนำให้สร้างลูปเพื่อดูการเปลี่ยนแปลงทั้งหมด โดยจะตรวจสอบ ว่ามีการเปลี่ยนแปลงข้อมูลที่พร้อมใช้งานหรือไม่ ขั้นตอนมีดังนี้

  1. เรียกใช้ getChanges โดยใช้โทเค็นเพื่อรับรายการการเปลี่ยนแปลง
  2. ตรวจสอบการเปลี่ยนแปลงแต่ละรายการว่าเป็นการเปลี่ยนแปลงประเภทUpsertionChange หรือDeletionChange แล้ว ดำเนินการที่จำเป็น
    • สำหรับ UpsertionChange ให้ใช้เฉพาะการเปลี่ยนแปลงที่ไม่ได้มาจากแอปโทรเพื่อไม่ให้นำเข้าข้อมูลซ้ำ
  3. กำหนดโทเค็น Changes ถัดไปเป็นโทเค็นใหม่
  4. ทำขั้นตอนที่ 1-3 ซ้ำจนกว่าจะไม่มีการเปลี่ยนแปลงเหลืออยู่
  5. จัดเก็บโทเค็นถัดไปและสงวนไว้สำหรับการนำเข้าในอนาคต
suspend fun processChanges(token: String): String {
    var nextChangesToken = token
    do {
        val response = healthConnectClient.getChanges(nextChangesToken)
        response.changes.forEach { change ->
            when (change) {
                is UpsertionChange ->
                    if (change.record.metadata.dataOrigin.packageName != context.packageName) {
                        processUpsertionChange(change)
                    }
                is DeletionChange -> processDeletionChange(change)
            }
        }
        nextChangesToken = response.nextChangesToken
    } while (response.hasMore)
    // Return and store the changes token for use next time.
    return nextChangesToken
}

ดูข้อควรพิจารณาในทางปฏิบัติเกี่ยวกับการดึงข้อมูลได้ที่แนวทางปฏิบัติแนะนำสำหรับซิงค์ข้อมูล

ประมวลผลการเปลี่ยนแปลงข้อมูล

แสดงการเปลี่ยนแปลงใน Datastore ของแอป สำหรับ UpsertionChange ให้ใช้ id และ lastModifiedTime จาก metadata เพื่อ upsert ระเบียน สำหรับ DeletionChange ให้ใช้ id ที่ระบุเพื่อลบระเบียน โดยคุณจะต้องจัดเก็บระเบียน id ตามที่ระบุไว้ใน จัดเก็บรหัส Health Connect

ลบข้อมูลออกจาก Health Connect

เมื่อผู้ใช้ลบข้อมูลของตนเองออกจากแอปของคุณ โปรดตรวจสอบว่าระบบได้นำข้อมูลดังกล่าวออกจาก Health Connect ด้วย ใช้ deleteRecords เพื่อดำเนินการนี้ ซึ่งใช้ประเภทระเบียนและรายการค่า id และ clientRecordId ทำให้สะดวกในการลบข้อมูลหลายรายการพร้อมกัน นอกจากนี้ ยังมีdeleteRecordstimeRangeFilter อีกด้วย

การซิงค์จากอุปกรณ์ที่สวมใส่ได้ที่มีเวลาในการตอบสนองต่ำ

หากต้องการซิงค์ข้อมูลจากอุปกรณ์ออกกำลังกายแบบสวมใส่ไปยัง Health Connect โดยมีความหน่วงต่ำ ให้ใช้ CompanionDeviceService วิธีนี้ใช้ได้กับอุปกรณ์ที่รองรับการแจ้งเตือนหรือการระบุ BLE GATT และกำหนดเป้าหมายเป็น Android 8.0 (API ระดับ 26) ขึ้นไป CompanionDeviceService ช่วยให้ แอปของคุณรับข้อมูลจากอุปกรณ์ที่สวมใส่ได้และเขียนข้อมูลลงใน Health Connect แม้ว่า แอปจะไม่ได้ทำงานอยู่ก็ตาม ดูรายละเอียดเพิ่มเติมเกี่ยวกับแนวทางปฏิบัติแนะนำสำหรับ BLE ได้ที่ ภาพรวมบลูทูธพลังงานต่ำ

เชื่อมโยงอุปกรณ์

ก่อนอื่น แอปของคุณต้องแนะนำผู้ใช้ผ่านกระบวนการแบบครั้งเดียวเพื่อเชื่อมโยง อุปกรณ์ที่สวมใส่ได้กับแอปของคุณโดยใช้ CompanionDeviceManager ซึ่งจะให้สิทธิ์ที่จำเป็นแก่แอปของคุณในการโต้ตอบกับอุปกรณ์ ดูข้อมูลเพิ่มเติมได้ที่การจับคู่อุปกรณ์ที่ใช้ร่วมกัน

ประกาศบริการในไฟล์ Manifest

จากนั้นประกาศ CompanionDeviceService ในไฟล์ Manifest ของแอป เพิ่มโค้ดต่อไปนี้ใน AndroidManifest.xml

<manifest ...>
   <application ...>
       <service
           android:name=".MyWearableService"
           android:exported="true"
           android:permission="android.permission.BIND_COMPANION_DEVICE_SERVICE">
           <intent-filter>
               <action android:name="android.companion.CompanionDeviceService" />
           </intent-filter>
       </service>
   </application>
</manifest>

สร้าง CompanionDeviceService

สุดท้าย ให้สร้างคลาสที่ขยาย CompanionDeviceService บริการนี้ จัดการการเชื่อมต่อกับอุปกรณ์ที่สวมใส่ได้และรับข้อมูลผ่านการเรียกกลับ BLE GATT เมื่อได้รับข้อมูลใหม่ ระบบจะเขียนข้อมูลดังกล่าวลงใน Health Connect ทันที

import android.companion.CompanionDeviceService
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
import android.bluetooth.BluetoothGattCharacteristic
import androidx.health.connect.client.permission.HealthPermission
import androidx.health.connect.client.HealthConnectClient
import androidx.health.connect.client.records.HeartRateRecord
import androidx.health.connect.client.records.StepsRecord
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch

class MyWearableService : CompanionDeviceService() {

   // A coroutine scope for handling suspend functions like writing to Health Connect
   private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
   private var healthConnectClient: HealthConnectClient? = null
   private var bluetoothGatt: BluetoothGatt? = null

   // This is called by the system when your wearable connects
   override fun onDeviceAppeared(address: String) {
       super.onDeviceAppeared(address)
       healthConnectClient = HealthConnectClient.getOrCreate(this)

       serviceScope.launch {
           // Check which permissions have been granted before subscribing to data from the wearable.
           // A service cannot request permissions, so your app must have already requested
           // and been granted them from an Activity.
           val granted = healthConnectClient?.permissionController?.getGrantedPermissions()

           // ... set up your GATT connection here ...

           // Once connected, subscribe to notifications for the data types you have
           // permission to write.
           if (granted?.contains(HealthPermission.getWritePermission(HeartRateRecord::class)) == true) {
               // subscribeToHeartRate(bluetoothGatt)
           }
       }
   }

   // The core of your low-latency pipeline is the BLE callback
   private val gattCallback = object : BluetoothGattCallback() {
       override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray) {
           super.onCharacteristicChanged(gatt, characteristic, value)

           // 1. Instantly receive the data
           val rawData = value

           // 2. Parse the data from the wearable
           val healthData = parseWearableData(rawData) // Your custom parsing logic

           // 3. Immediately process it. For simplicity, this example writes
           //    directly to Health Connect. A real-world app might write to its
           //    own datastore first and then sync with Health Connect.
           serviceScope.launch {
               writeToHealthConnect(healthData)
           }
       }
   }

   private suspend fun writeToHealthConnect(healthData: HealthData) {
       val records = prepareHealthConnectRecords(healthData) // Convert to Health Connect records
       try {
           healthConnectClient?.insertRecords(records)
       } catch (e: Exception) {
           // Handle exceptions
       }
   }

   // This is called by the system when your wearable disconnects
   override fun onDeviceDisappeared(address: String) {
       super.onDeviceDisappeared(address)
       // Clean up your GATT connection and other resources
       bluetoothGatt?.close()
   }
}

แนวทางปฏิบัติแนะนำสำหรับการซิงค์ข้อมูล

ปัจจัยต่อไปนี้มีผลต่อกระบวนการซิงค์

การหมดอายุของโทเค็น

เนื่องจากโทเค็นการเปลี่ยนแปลงที่ไม่ได้ใช้จะหมดอายุภายใน 30 วัน คุณจึงต้องใช้กลยุทธ์การซิงค์ ที่หลีกเลี่ยงการสูญเสียข้อมูลในกรณีดังกล่าว กลยุทธ์ของคุณอาจ รวมถึงแนวทางต่อไปนี้

  • ค้นหาที่เก็บข้อมูลแอปสำหรับบันทึกที่ใช้ล่าสุดซึ่งมี id จาก Health Connect ด้วย
  • ขอระเบียนจาก Health Connect ที่เริ่มต้นด้วย การประทับเวลาที่เฉพาะเจาะจง จากนั้นแทรกหรืออัปเดตระเบียนเหล่านั้นใน Datastore ของแอป
  • ขอโทเค็นการเปลี่ยนแปลงเพื่อจองไว้สำหรับครั้งถัดไปที่จำเป็น

กลยุทธ์การจัดการการเปลี่ยนแปลงที่แนะนำ

ในกรณีที่แอปได้รับโทเค็นการเปลี่ยนแปลงที่ไม่ถูกต้องหรือหมดอายุ เราขอแนะนำกลยุทธ์การจัดการต่อไปนี้โดยขึ้นอยู่กับการใช้งานในตรรกะของคุณ

  • อ่านและขจัดข้อมูลที่ซ้ำกันทั้งหมด นี่เป็นกลยุทธ์ที่เหมาะที่สุด
    • จัดเก็บการประทับเวลาของครั้งล่าสุดที่แอปอ่านข้อมูลจาก Health Connect
    • เมื่อโทเค็นหมดอายุ ให้อ่านข้อมูลทั้งหมดอีกครั้งจากแสตมป์เวลาล่าสุดหรือในช่วง 30 วันที่ผ่านมา จากนั้นขจัดข้อมูลที่ซ้ำกันกับข้อมูลที่อ่านก่อนหน้านี้โดยใช้ตัวระบุ
    • ขอแนะนำให้ติดตั้งใช้งาน Client-ID เนื่องจากจำเป็นสำหรับการอัปเดตข้อมูล
  • อ่านเฉพาะข้อมูลตั้งแต่การประทับเวลาการอ่านครั้งล่าสุด ซึ่งจะส่งผลให้ข้อมูลบางอย่างไม่ตรงกันในช่วงเวลาที่โทเค็นการเปลี่ยนแปลงหมดอายุ แต่ระยะเวลาจะสั้นลง โดยอาจใช้เวลา 2-3 ชั่วโมงถึง 2-3 วัน
    • จัดเก็บการประทับเวลาของครั้งล่าสุดที่แอปอ่านข้อมูลจาก Health Connect
    • เมื่อโทเค็นหมดอายุ ให้อ่านข้อมูลทั้งหมดตั้งแต่การประทับเวลาเป็นต้นไป
  • ลบแล้วอ่านข้อมูลในช่วง 30 วันที่ผ่านมา ซึ่งสอดคล้องกับสิ่งที่เกิดขึ้นในการผสานรวมครั้งแรกมากขึ้น
    • ลบข้อมูลทั้งหมดที่แอปอ่านจาก Health Connect ในช่วง 30 วันที่ผ่านมา
    • เมื่อลบแล้ว ให้อ่านข้อมูลทั้งหมดนี้อีกครั้ง
  • อ่านข้อมูลในช่วง 30 วันที่ผ่านมาโดยไม่กรองข้อมูลที่ซ้ำกัน กลยุทธ์นี้เป็นกลยุทธ์ที่แย่ที่สุด และทำให้ข้อมูลที่ซ้ำกันแสดงต่อผู้ใช้
    • ลบข้อมูลทั้งหมดที่แอปอ่านจาก Health Connect ในช่วง 30 วันที่ผ่านมา
    • อนุญาตรายการที่ซ้ำกัน

โทเค็นการเปลี่ยนแปลงประเภทข้อมูล

หากแอปใช้ข้อมูลมากกว่า 1 ประเภทโดยอิสระ ให้ใช้โทเค็นการเปลี่ยนแปลงแยกกันสำหรับข้อมูลแต่ละประเภท ใช้รายการประเภทข้อมูลหลายรายการกับ Changes Sync API เฉพาะในกรณีที่ประเภทข้อมูลเหล่านี้ใช้ร่วมกันหรือไม่ได้ใช้เลย

การอ่านเบื้องหน้า

แอปจะอ่านข้อมูลจาก Health Connect ได้เฉพาะในขณะที่แอปอยู่เบื้องหน้า เมื่อซิงค์ข้อมูลจาก Health Connect การเข้าถึง Health Connect อาจถูกขัดจังหวะได้ทุกเมื่อ ตัวอย่างเช่น แอปของคุณต้องจัดการการหยุดชะงัก กลางการซิงค์เมื่ออ่านข้อมูลจำนวนมากจาก Health Connect และดำเนินการต่อในครั้งถัดไปที่เปิดแอป

การอ่านในเบื้องหลัง

คุณขอให้แอปพลิเคชันทำงานในเบื้องหลังและอ่านข้อมูลจาก Health Connect ได้ หากคุณขอสิทธิ์ Background Read ผู้ใช้จะให้สิทธิ์แอปของคุณ เข้าถึงเพื่ออ่านข้อมูลในเบื้องหลังได้

กำหนดเวลาการนำเข้า

เนื่องจากแอปไม่ได้รับการแจ้งเตือนเมื่อมีข้อมูลใหม่ คุณจึงต้องตรวจสอบข้อมูลใหม่ใน 2 จุดต่อไปนี้

  • ทุกครั้งที่แอปของคุณทำงานอยู่เบื้องหน้า ในกรณีนี้ ให้ใช้ เหตุการณ์วงจร
  • เป็นระยะๆ ขณะที่แอปยังคงทำงานอยู่เบื้องหน้า แจ้งผู้ใช้เมื่อมีข้อมูลใหม่พร้อมใช้งาน เพื่อให้ผู้ใช้สามารถอัปเดตหน้าจอให้แสดงการเปลี่ยนแปลงได้