ซิงค์ข้อมูล

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

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

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

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

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

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

ส่วนแรกของกระบวนการซิงค์คือการป้อนข้อมูลจาก Datastore ของแอปไปยัง Datastore ของ 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(
            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 การ Upsert ข้อมูลหมายความว่าระบบจะเขียนทับข้อมูลที่มีอยู่ใน Health Connect ตราบใดที่ค่า clientRecordId มีอยู่ใน Datastore ของ Health Connect และ clientRecordVersion สูงกว่าค่าที่มีอยู่ ไม่เช่นนั้น ระบบจะเขียนข้อมูลที่ Upsert เป็นข้อมูลใหม่

    healthConnectClient.insertRecords(arrayListOf(record))

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

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

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

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

val response = healthConnectClient.insertRecords(listOf(record))
for (recordId in response.recordIdsList) {
    // Store recordId to your app's datastore
}

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

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

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

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

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

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

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

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

  1. เรียก getChanges โดยใช้โทเค็นเพื่อรับรายการ การเปลี่ยนแปลง
  2. ตรวจสอบการเปลี่ยนแปลงแต่ละรายการว่าประเภทการเปลี่ยนแปลงเป็น UpsertionChange หรือ DeletionChange แล้ว ดำเนินการที่จำเป็น
    • สำหรับ UpsertionChange ให้ใช้เฉพาะการเปลี่ยนแปลงที่ไม่ได้มาจากแอปการโทรเพื่อให้แน่ใจว่าคุณจะไม่นำเข้าข้อมูลอีกครั้ง
  3. กำหนดโทเค็น การเปลี่ยนแปลง ถัดไปเป็นโทเค็นใหม่
  4. ทำซ้ำขั้นตอนที่ 1-3 จนกว่าจะไม่มี การเปลี่ยนแปลง เหลืออยู่
  5. จัดเก็บโทเค็นถัดไปและเก็บไว้สำหรับการนำเข้าในอนาคต

suspend fun processChanges(context: Context, 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 ทันที

private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private var healthConnectClient: HealthConnectClient? = null
private var bluetoothGatt: BluetoothGatt? = null

override fun onDeviceAppeared(address: String) {
    super.onDeviceAppeared(address)
    healthConnectClient = HealthConnectClient.getOrCreate(this)

    serviceScope.launch {
        val granted = healthConnectClient?.permissionController?.getGrantedPermissions()

        // 1. Check permissions ONCE when the device connects
        if (granted?.contains(HealthPermission.getWritePermission(HeartRateRecord::class)) ?: false) {
            // This is where you'd actually start the Bluetooth connection
            // bluetoothGatt = gattCallback.connect(...)
        }

        // 2. Do your initial database read
        readExerciseSessionAndRoute()
    }
}

private val gattCallback = object : BluetoothGattCallback() {
    override fun onCharacteristicChanged(
        gatt: BluetoothGatt,
        characteristic: BluetoothGattCharacteristic,
        value: ByteArray
    ) {
        super.onCharacteristicChanged(gatt, characteristic, value)

        // 3. ONLY process the incoming data here
        val rawData = value

        serviceScope.launch {
            // parseWearableData(rawData)
            // insertExerciseRoute() or writeToHealthConnect()
        }
    }
}

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

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

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

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

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

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

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

  • อ่านและลบข้อมูลที่ซ้ำกันทั้งหมด ซึ่งเป็นกลยุทธ์ที่เหมาะสมที่สุด
    • จัดเก็บการประทับเวลาของครั้งล่าสุดที่อ่านข้อมูลจาก Health Connect
    • เมื่อโทเค็นหมดอายุ ให้อ่านข้อมูลทั้งหมดอีกครั้งจากการประทับเวลาล่าสุดหรือในช่วง 30 วันที่ผ่านมา จากนั้นลบข้อมูลที่ซ้ำกันกับข้อมูลที่อ่านก่อนหน้านี้โดยใช้ตัวระบุ
    • เราขอแนะนำให้ใช้รหัสไคลเอ็นต์เนื่องจากจำเป็นสำหรับการอัปเดตข้อมูล
  • อ่านเฉพาะข้อมูลตั้งแต่การประทับเวลาที่อ่านครั้งล่าสุด วิธีนี้จะทำให้ข้อมูลไม่ตรงกันบ้างในช่วงเวลาที่โทเค็นการเปลี่ยนแปลงหมดอายุ แต่ระยะเวลาจะสั้นกว่า โดยอาจใช้เวลา 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 จุดต่อไปนี้

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