סנכרון נתונים

המדריך הזה תואם לגרסה 1.1.0‎-alpha12 של Health Connect ואילך.

לרוב האפליקציות שמשולבות עם Health Connect יש מאגר נתונים משלהן שמשמש כמקור המידע המהימן. אפליקציית Health Connect מספקת דרכים לסנכרן את האפליקציה שלכם.

בהתאם לארכיטקטורה של האפליקציה, תהליך הסנכרון עשוי לכלול חלק מהפעולות הבאות או את כולן:

  • להעביר נתונים חדשים או מעודכנים ממאגר הנתונים של האפליקציה אל Health Connect.
  • שולפים שינויים בנתונים מ-Health Connect למאגר הנתונים של האפליקציה.
  • למחוק נתונים מ-Health Connect כשהם נמחקים ממאגר הנתונים של האפליקציה.

בכל מקרה, חשוב לוודא שתהליך הסנכרון שומר על התאמה בין Health Connect לבין מאגר הנתונים של האפליקציה.

הזנת נתונים ב-Health Connect

החלק הראשון בתהליך הסנכרון הוא להזין נתונים ממאגר הנתונים של האפליקציה למאגר הנתונים של Health Connect.

הכנת הנתונים

בדרך כלל, הרשומות במאגר הנתונים של האפליקציה כוללות את הפרטים הבאים:

  • מפתח ייחודי, כמו UUID.
  • גרסה או חותמת זמן.

כשמסנכרנים נתונים עם Health Connect, צריך לזהות ולהזין רק את הנתונים שנוספו, עודכנו או נמחקו מאז הסנכרון האחרון.

כתיבת נתונים ב-Health Connect

כדי להזין נתונים ב-Health Connect, פועלים לפי השלבים הבאים:

  1. לקבל רשימה של רשומות חדשות, מעודכנות או שנמחקו ממאגר הנתונים של האפליקציה.
  2. לכל רשומה, יוצרים אובייקט Record שמתאים לסוג הנתונים הזה. לדוגמה, אפשר ליצור אובייקט WeightRecord לנתונים שקשורים למשקל.
  3. מציינים אובייקט Metadata עם כל Record. הוא כולל את clientRecordId, שהוא מזהה ממאגר הנתונים של האפליקציה שבאמצעותו אפשר לזהות את הרשומה באופן ייחודי. אפשר להשתמש במפתח הייחודי הקיים. אם הנתונים שלכם כוללים גרסאות, צריך לספק גם 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. עדכון או הוספה (upsert) של נתונים פירושו שכל הנתונים הקיימים ב-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(listOf(record))
for (recordId in response.recordIdsList) {
    // Store recordId to your app's datastore
}

שליפת נתונים מ-Health Connect

החלק השני של תהליך הסנכרון הוא שליפת שינויים בנתונים מ-Health Connect למאגר הנתונים של האפליקציה. השינויים בנתונים יכולים לכלול עדכונים ומחיקות.

קבלת אסימון שינויים

כדי לקבל רשימה של שינויים שצריך לשלוף מ-Health Connect, האפליקציה צריכה לעקוב אחרי טוקנים של שינויים. אתם יכולים להשתמש בהם כשאתם מבקשים שינויים כדי להחזיר גם רשימה של שינויים בנתונים וגם אסימון שינויים חדש לשימוש בפעם הבאה.

כדי לקבל טוקן של Changes, צריך להפעיל את getChangesToken ולספק את סוגי הנתונים הנדרשים.

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

בדיקה אם יש שינויים בנתונים

אחרי שקיבלתם אסימון Changes, אתם יכולים להשתמש בו כדי לקבל את כל ה-Changes. מומלץ ליצור לולאה שתעבור על כל ה-Changes ותבדוק אם יש שינויים זמינים בנתונים. אלה השלבים:

  1. קוראים ל-getChanges באמצעות האסימון כדי לקבל רשימה של שינויים.
  2. בודקים כל שינוי כדי לראות אם סוג השינוי הוא UpsertionChange או DeletionChange, ומבצעים את הפעולות הנדרשות.
    • ב-UpsertionChange, צריך לקבל רק שינויים שלא הגיעו מהאפליקציה שמבצעת את הקריאה, כדי לוודא שאתם לא מייבאים מחדש נתונים.
  3. מקצים את האסימון הבא של Changes כאסימון החדש.
  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
}

כדי לקבל מידע על השיקולים המעשיים לשליפת נתונים, כדאי לעיין בשיטות המומלצות לסנכרון נתונים.

שינויים בתהליך עיבוד הנתונים

השינויים יופיעו במאגר הנתונים של האפליקציה. ב-UpsertionChange, משתמשים ב-id וב-lastModifiedTime מה-metadata שלו כדי לעדכן או להוסיף (upsert) את הרשומה. ב-DeletionChange, משתמשים ב-id שסופק כדי למחוק את הרשומה. לשם כך, צריך לאחסן את הרשומה id כמו שמתואר בקטע אחסון מזהים של Health Connect.

מחיקת נתונים מ-Health Connect

כשמשתמש מוחק את הנתונים שלו מהאפליקציה, חשוב לוודא שהנתונים נמחקים גם מ-Health Connect. כדי לעשות זאת, משתמשים ב-deleteRecords. הפונקציה הזו מקבלת סוג רשומה ורשימה של ערכי id ו-clientRecordId, וכך קל למחוק כמה נתונים בבת אחת. יש גם deleteRecords חלופי שמקבל timeRangeFilter.

סנכרון עם זמן אחזור קצר ממכשירים לבישים

כדי לסנכרן נתונים ממכשיר כושר לביש ל-Health Connect עם זמן אחזור קצר, משתמשים ב-CompanionDeviceService. הגישה הזו מתאימה למכשירים שתומכים בהתראות או באינדיקציות של BLE GATT, ומטרגטים את Android מגרסה 8.0 (רמת API‏ 26) ואילך. CompanionDeviceService מאפשר לאפליקציה לקבל נתונים ממכשירים לבישים ולכתוב אותם ב-Health Connect, גם כשהאפליקציה לא פועלת. מידע נוסף על שיטות מומלצות ל-BLE זמין במאמר סקירה כללית של Bluetooth עם צריכת אנרגיה נמוכה.

שיוך המכשיר

קודם כל, האפליקציה צריכה להדריך את המשתמש בתהליך חד-פעמי לשיוך המכשיר הלביש לאפליקציה באמצעות CompanionDeviceManager. כך האפליקציה מקבלת את ההרשאות שנדרשות כדי לקיים אינטראקציה עם המכשיר. מידע נוסף על התאמת מכשירים נלווים

הצהרה על השירות במניפסט

בשלב הבא, מצהירים על CompanionDeviceService בקובץ המניפסט של האפליקציה. מוסיפים את הקוד הבא לקובץ 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. השירות הזה מטפל בחיבור למכשיר הלביש ומקבל נתונים באמצעות קריאות חוזרות (callbacks) של 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()
        }
    }
}

שיטות מומלצות לסנכרון נתונים

הגורמים הבאים משפיעים על תהליך הסנכרון.

תפוגת התוקף של הטוקן

אסימון Changes שלא נעשה בו שימוש פג תוך 30 יום, לכן צריך להשתמש באסטרטגיית סנכרון שמונעת אובדן מידע במקרה כזה. האסטרטגיה יכולה לכלול את הגישות הבאות:

  • מחפשים במאגר הנתונים של האפליקציה את הרשומה האחרונה שנצרכה, שיש לה גם id מ-Health Connect.
  • מבקשים רשומות מ-Health Connect שמתחילות בחותמת זמן ספציפית, ואז מוסיפים או מעדכנים אותן במאגר הנתונים של האפליקציה.
  • מבקשים אסימון שינויים כדי לשמור אותו לפעם הבאה שיהיה בו צורך.

שיטות לניהול שינויים מומלצים

אם האפליקציה מקבלת אסימוני שינויים לא חוקיים או שפג תוקפם, מומלץ להשתמש באסטרטגיות הניהול הבאות, בהתאם לאופן שבו הן מיושמות בלוגיקה שלכם:

  • קריאה וביטול כפילויות של כל הנתונים. זו השיטה האידיאלית.
    • לשמור את חותמת הזמן של הפעם האחרונה שבה הן קראו נתונים מ-Health Connect.
    • כשהאסימון יפוג, קראו מחדש את כל הנתונים מחותמת הזמן האחרונה או מ-30 הימים האחרונים. לאחר מכן, מבטלים את הכפילויות בהשוואה לנתונים שנקראו קודם באמצעות מזהים.
    • מומלץ להטמיע מזהי לקוחות כי הם נדרשים לעדכוני נתונים.
  • לקרוא רק נתונים שנוספו מאז חותמת הזמן של הקריאה האחרונה. כתוצאה מכך, יש פערים מסוימים בנתונים בסביבות מועד התפוגה של אסימון השינויים, אבל התקופה קצרה יותר ויכולה להימשך בין כמה שעות לכמה ימים.
    • לשמור את חותמת הזמן של הפעם האחרונה שבה הן קראו נתונים מ-Health Connect.
    • כשתוקף האסימון יפוג, כל הנתונים ייקראו מחותמת הזמן הזו והלאה.
  • מחיקה ואז קריאה של הנתונים מ-30 הימים האחרונים. זה דומה יותר למה שקורה בשילוב הראשון.
    • למחוק את כל הנתונים שהאפליקציה קראה מ-Health Connect ב-30 הימים האחרונים.
    • אחרי המחיקה, קוראים שוב את כל הנתונים האלה.
  • קריאת נתונים מ-30 הימים האחרונים בלי ביטול כפילויות. זו האסטרטגיה הכי פחות אידיאלית, והיא גורמת להצגת נתונים כפולים למשתמשים.
    • למחוק את כל הנתונים שהאפליקציה קראה מ-Health Connect ב-30 הימים האחרונים.
    • אפשר להזין ערכים כפולים.

אסימונים של שינויים בסוג הנתונים

אם האפליקציה צורכת יותר מסוג נתונים אחד באופן עצמאי, צריך להשתמש באסימוני שינויים נפרדים לכל סוג נתונים. השתמשו ברשימה של כמה סוגי נתונים עם Changes Sync API רק אם סוגי הנתונים האלה נצרכים ביחד או לא נצרכים בכלל.

קריאות בחזית

אפליקציות יכולות לקרוא נתונים מ-Health Connect רק כשהן פועלות בחזית. כשמסנכרנים נתונים מ-Health Connect, הגישה ל-Health Connect עלולה להיקטע בכל שלב. לדוגמה, האפליקציה צריכה לטפל בהפרעות באמצע הסנכרון כשקוראים כמות גדולה של נתונים מ-Health Connect, ולהמשיך בפעם הבאה שהאפליקציה נפתחת.

קריאה ברקע

אתם יכולים לבקש שהאפליקציה תפעל ברקע ותקרא נתונים מ-Health Connect. אם תבקשו את ההרשאה Background Read, המשתמש יוכל לתת לאפליקציה גישה לקריאת נתונים ברקע.

ייבוא תזמונים

האפליקציה לא יכולה לקבל התראות על נתונים חדשים, לכן צריך לבדוק אם יש נתונים חדשים בשני מקרים:

  • בכל פעם שהאפליקציה הופכת לפעילה בחזית. במקרה כזה, משתמשים באירועים במחזור החיים.
  • מדי פעם, כשהאפליקציה בחזית. כדאי להודיע למשתמשים כשיש נתונים חדשים, כדי שהם יוכלו לעדכן את המסך בהתאם לשינויים.