Как синхронизировать данные

Это руководство совместимо с версией 1.1.0-alpha12 платформы "Здоровье и спорт".

Большинство приложений, интегрированных с платформой "Здоровье и спорт", имеют собственное хранилище данных, которое является источником достоверной информации. В Health Connect есть инструменты, которые помогут вам поддерживать синхронизацию в приложении.

В зависимости от архитектуры приложения процесс синхронизации может включать некоторые или все из следующих действий:

  • Передавать новые или обновленные данные из хранилища приложения в сервис "Здоровье и спорт".
  • Получать изменения данных из Здоровья и спорта в хранилище данных приложения.
  • Удалять данные из сервиса "Здоровье и спорт", когда они удаляются из хранилища данных приложения.

В каждом случае убедитесь, что в процессе синхронизации данные в сервисе "Здоровье и спорт" и хранилище данных вашего приложения остаются согласованными.

Как передавать данные в Здоровье и спорт

Первый этап синхронизации – передача данных из хранилища приложения в хранилище сервиса "Здоровье и спорт".

Подготовка данных

Обычно записи в хранилище данных приложения содержат следующие сведения:

  • Уникальный ключ, например UUID.
  • версию или временную метку;

При синхронизации данных с Здоровье и спорт идентифицируйте и передавайте только те данные, которые были добавлены, обновлены или удалены с момента последней синхронизации.

Записывать данные в Здоровье и спорт

Чтобы передавать данные в сервис "Здоровье и спорт", выполните следующие действия:

  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. Добавлять или обновлять данные в сервисе "Здоровье и спорт" с помощью insertRecords. При этом существующие данные в сервисе "Здоровье и спорт" перезаписываются, если значения clientRecordId есть в хранилище данных сервиса, а значение clientRecordVersion выше существующего. В противном случае данные, добавленные с помощью операции upsert, будут записаны как новые.

    healthConnectClient.insertRecords(arrayListOf(record))

Чтобы узнать, как правильно передавать данные, ознакомьтесь с рекомендациями по записи данных.

Хранение идентификаторов Health Connect

Если ваше приложение также считывает данные из Здоровье и спорт, сохраняйте id для записей после их вставки или обновления. Этот id необходим для обработки удалений при получении изменений данных из Здоровья и спорта.

Функция insertRecords возвращает объект InsertRecordsResponse, содержащий список значений id. Используйте ответ, чтобы получить идентификаторы записей и сохранить их.

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

Получать данные из Здоровья и спорта

Вторая часть процесса синхронизации – это получение изменений данных из сервиса "Здоровье и спорт" в хранилище данных вашего приложения. Изменения могут включать обновления и удаления.

Как получить токен изменений

Чтобы получить список изменений из сервиса "Здоровье и спорт", приложению нужно отслеживать токены Changes. Их можно использовать при запросе Changes, чтобы получить как список изменений данных, так и новый токен Changes для следующего запроса.

Чтобы получить токен Changes, вызовите getChangesToken и укажите необходимые типы данных.

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

Проверка изменений данных

Получив токен 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, чтобы обновить или вставить запись. Для DeletionChange используйте id, чтобы удалить запись. Для этого необходимо сохранить запись id, как описано в разделе Хранение идентификаторов Здоровья и спорта.

Как удалить данные из сервиса "Здоровье и спорт"

Если пользователь удаляет свои данные из вашего приложения, убедитесь, что они также удаляются из сервиса "Здоровье и спорт". Для этого используйте deleteRecords. Он принимает тип записи и список значений id и clientRecordId, что позволяет удобно удалять несколько наборов данных. Также доступна альтернативная функция deleteRecords, которая принимает значение timeRangeFilter.

Синхронизация с носимыми устройствами с низкой задержкой

Чтобы синхронизировать данные с носимого фитнес-устройства с платформой "Здоровье и спорт" с низкой задержкой, используйте CompanionDeviceService. Этот подход работает для устройств, которые поддерживают уведомления или индикаторы BLE GATT и предназначены для Android 8.0 (уровень API 26) или более поздней версии. CompanionDeviceService позволяет вашему приложению получать данные с носимых устройств и записывать их в Здоровье и спорт, даже если приложение не запущено. Дополнительную информацию о рекомендациях по использованию 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. Этот сервис устанавливает подключение к носимому устройству и получает данные через обратные вызовы BLE GATT. Когда поступают новые данные, они сразу же записываются на платформу "Здоровье и спорт".

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 из Здоровья и спорта.
  • Запрашивать записи из Здоровья и спорта, начиная с определенной временной метки, а затем вставлять или обновлять их в хранилище данных приложения.
  • Запросите токен изменений, чтобы зарезервировать его для следующего использования.

Рекомендуемые стратегии управления изменениями

Если ваше приложение получает недействительные или устаревшие токены Changes, мы рекомендуем использовать следующие стратегии управления в зависимости от того, как они применяются в вашей логике:

  • Прочитать и дедуплицировать все данные. Это наиболее подходящая стратегия.
    • Хранить временную метку последнего считывания данных из Health Connect.
    • По истечении срока действия токена повторно прочитайте все данные с последней временной метки или за последние 30 дней. Затем сравните его с ранее прочитанными данными, используя идентификаторы.
    • Рекомендуем реализовать Client-ID, поскольку они необходимы для обновления данных.
  • Считывать данные только с момента последней временной метки. Из-за этого в момент истечения срока действия токена Changes могут возникать расхождения в данных, но они будут наблюдаться в течение короткого периода времени – от нескольких часов до пары дней.
    • Хранить временную метку последнего считывания данных из Health Connect.
    • По истечении срока действия токена считываются все данные, начиная с указанной временной метки.
  • Удалить, а затем прочитать данные за последние 30 дней. Это больше соответствует тому, что происходит при первой интеграции.
    • Удалить все данные, считанные приложением из Health Connect за последние 30 дней.
    • После удаления прочитайте все эти данные ещё раз.
  • Прочитать данные за последние 30 дней без дедупликации. Это наименее подходящая стратегия, поскольку она приводит к тому, что пользователям показываются дублирующиеся данные.
    • Удалить все данные, которые приложение прочитало из Health Connect за последние 30 дней.
    • Разрешить дублирующиеся записи.

Токены изменений типа данных

Если ваше приложение использует несколько типов данных независимо друг от друга, для каждого типа данных используйте отдельные токены Changes. Используйте список нескольких типов данных с Changes Sync API, только если эти типы данных используются вместе или не используются вообще.

Чтение на переднем плане

Приложения могут считывать данные с платформы "Здоровье и спорт", только когда они открыты. При синхронизации данных с платформы "Здоровье и спорт" доступ к ней может быть прерван в любой момент. Например, ваше приложение должно уметь обрабатывать прерывания синхронизации при чтении большого объема данных из сервиса "Здоровье и спорт" и продолжать синхронизацию при следующем запуске.

Чтение в фоновом режиме

Вы можете запросить разрешение на то, чтобы ваше приложение работало в фоновом режиме и считывало данные с платформы "Здоровье и спорт". Если вы запросите разрешение Background Read, пользователь сможет предоставить приложению доступ к чтению данных в фоновом режиме.

Время импорта

Поскольку приложение не может получать уведомления о новых данных, проверять их нужно в двух случаях:

  • Каждый раз, когда приложение становится активным на переднем плане. В этом случае используйте события жизненного цикла.
  • Периодически, когда приложение открыто. Уведомлять пользователей о наличии новых данных, чтобы они могли обновить экран и увидеть изменения.