Запись данных

Это руководство совместимо с Health Connect версии 1.1.0-alpha12 .

В этом руководстве описывается процесс записи или обновления данных в Health Connect.

Обработка нулевых значений

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

Настройте структуру данных

Перед записью данных необходимо настроить записи. Более 50 типов данных имеют свою собственную структуру. Подробнее о доступных типах данных см. в справочнике Jetpack .

Основные записи

Тип данных «Шаги» в Health Connect фиксирует количество шагов, пройденных пользователем между измерениями. Количество шагов — это распространённый показатель на платформах для здоровья, фитнеса и оздоровления.

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

val endTime = Instant.now()
val startTime = endTime.minus(Duration.ofMinutes(15))

val stepsRecord = StepsRecord(
    count = 120,
    startTime = startTime,
    endTime = endTime,
    startZoneOffset = ZoneOffset.UTC,
    endZoneOffset = ZoneOffset.UTC,
    metadata = Metadata.autoRecorded(
        device = Device(type = Device.TYPE_WATCH)
    )
)

Записи с единицами измерения

Health Connect может хранить значения вместе с единицами измерения для обеспечения точности. Одним из примеров является тип данных «Питание» (Nutrition) , который является обширным и всеобъемлющим. Он включает в себя широкий спектр дополнительных полей с данными о питательных веществах, от общего содержания углеводов до витаминов. Каждая точка данных представляет собой количество питательных веществ, которые потенциально были потреблены в составе приёма пищи или продукта.

В этом типе данных все питательные вещества представлены в единицах Mass , а energy представлена в единицах Energy .

В следующем примере показано, как установить данные о питании для пользователя, съевшего банан:

val endTime = Instant.now()
val startTime = endTime.minus(Duration.ofMinutes(1))

val banana = NutritionRecord(
    name = "banana",
    energy = 105.0.kilocalories,
    dietaryFiber = 3.1.grams,
    potassium = 0.422.grams,
    totalCarbohydrate = 27.0.grams,
    totalFat = 0.4.grams,
    saturatedFat = 0.1.grams,
    sodium = 0.001.grams,
    sugar = 14.0.grams,
    vitaminB6 = 0.0005.grams,
    vitaminC = 0.0103.grams,
    startTime = startTime,
    endTime = endTime,
    startZoneOffset = ZoneOffset.UTC,
    endZoneOffset = ZoneOffset.UTC,
    metadata = Metadata.manualEntry(
        device = Device(type = Device.TYPE_PHONE)
    )
)

Записи с серийными данными

Health Connect может хранить список серий данных. Одним из примеров является тип данных «Частота сердечных сокращений» , который фиксирует серию измерений частоты сердечных сокращений между измерениями.

В этом типе данных samples параметров представлены списком выборок частоты сердечных сокращений . Каждая выборка содержит значение beatsPerMinute и значение time .

В следующем примере показано, как настроить данные серии пульса:

val endTime = Instant.now()
val startTime = endTime.minus(Duration.ofMinutes(5))

val heartRateRecord = HeartRateRecord(
    startTime = startTime,
    startZoneOffset = ZoneOffset.UTC,
    endTime = endTime,
    endZoneOffset = ZoneOffset.UTC,
    // records 10 arbitrary data, to replace with actual data
    samples = List(10) { index ->
        HeartRateRecord.Sample(
            time = startTime + Duration.ofSeconds(index.toLong()),
            beatsPerMinute = 100 + index.toLong(),
        )
    },
    metadata = Metadata.autoRecorded(
        device = Device(type = Device.TYPE_WATCH)
    ))

Запросить разрешения у пользователя

После создания клиентского экземпляра ваше приложение должно запрашивать разрешения у пользователя. Пользователи должны иметь возможность предоставлять или отклонять разрешения в любое время.

Для этого создайте набор разрешений для необходимых типов данных. Убедитесь, что разрешения в наборе предварительно объявлены в манифесте Android.

// Create a set of permissions for required data types
val PERMISSIONS =
    setOf(
  HealthPermission.getReadPermission(HeartRateRecord::class),
  HealthPermission.getWritePermission(HeartRateRecord::class),
  HealthPermission.getReadPermission(StepsRecord::class),
  HealthPermission.getWritePermission(StepsRecord::class)
)

Используйте getGrantedPermissions , чтобы проверить, предоставлены ли приложению необходимые разрешения. Если нет, запросите их с помощью createRequestPermissionResultContract . Откроется экран разрешений Health Connect.

// Create the permissions launcher
val requestPermissionActivityContract = PermissionController.createRequestPermissionResultContract()

val requestPermissions = registerForActivityResult(requestPermissionActivityContract) { granted ->
  if (granted.containsAll(PERMISSIONS)) {
    // Permissions successfully granted
  } else {
    // Lack of required permissions
  }
}

suspend fun checkPermissionsAndRun(healthConnectClient: HealthConnectClient) {
  val granted = healthConnectClient.permissionController.getGrantedPermissions()
  if (granted.containsAll(PERMISSIONS)) {
    // Permissions already granted; proceed with inserting or reading data
  } else {
    requestPermissions.launch(PERMISSIONS)
  }
}

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

Запись данных

Один из распространённых рабочих процессов в Health Connect — запись данных. Для добавления записей используйте insertRecords .

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

suspend fun insertSteps(healthConnectClient: HealthConnectClient) {
    val endTime = Instant.now()
    val startTime = endTime.minus(Duration.ofMinutes(5))
    try {
        val stepsRecord = StepsRecord(
            count = 120,
            startTime = startTime,
            endTime = endTime,
            startZoneOffset = ZoneOffset.UTC,
            endZoneOffset = ZoneOffset.UTC,
            metadata = Metadata.autoRecorded(
                device = Device(type = Device.TYPE_WATCH)
            )
        )
        healthConnectClient.insertRecords(listOf(stepsRecord))
    } catch (e: Exception) {
        // Run error handling here
    }
}

Обновление данных

Если вам нужно изменить одну или несколько записей, особенно если вам нужно синхронизировать хранилище данных приложения с данными Health Connect, вы можете обновить данные. Существует два способа обновления существующих данных, в зависимости от идентификатора, используемого для поиска записей.

Метаданные

Сначала стоит изучить класс Metadata , поскольку он необходим при обновлении данных. При создании каждая Record в Health Connect имеет поле metadata . Следующие свойства важны для синхронизации:

Характеристики Описание
id Каждая Record в Health Connect имеет уникальное значение id .
Health Connect автоматически заполняет это поле при добавлении новой записи.
lastModifiedTime Каждая Record также отслеживает время последнего изменения записи.
Health Connect заполняет эту информацию автоматически.
clientRecordId Каждая Record может иметь связанный с ней уникальный идентификатор, который будет использоваться в качестве ссылки в хранилище данных вашего приложения.
Ваше приложение предоставляет это значение.
clientRecordVersion Если запись имеет clientRecordId , clientRecordVersion можно использовать для синхронизации данных с версией в хранилище данных вашего приложения.
Ваше приложение предоставляет это значение.

Обновление после прочтения по временному диапазону

Чтобы обновить данные, сначала подготовьте необходимые записи. При необходимости внесите в записи изменения. Затем вызовите updateRecords для внесения изменений.

В следующем примере показано, как обновить данные. Для этого значения смещения зоны каждой записи приводятся в соответствие с тихоокеанским стандартным временем (PST).

suspend fun updateSteps(
    healthConnectClient: HealthConnectClient,
    prevRecordStartTime: Instant,
    prevRecordEndTime: Instant
) {
    try {
        val request = healthConnectClient.readRecords(
            ReadRecordsRequest(
                recordType = StepsRecord::class, timeRangeFilter = TimeRangeFilter.between(
                    prevRecordStartTime, prevRecordEndTime
                )
            )
        )

        val newStepsRecords = arrayListOf<StepsRecord>()
        for (record in request.records) {
            // Adjusted both offset values to reflect changes
            val sr = StepsRecord(
                count = record.count,
                startTime = record.startTime,
                startZoneOffset = record.startTime.atZone(ZoneId.of("PST")).offset,
                endTime = record.endTime,
                endZoneOffset = record.endTime.atZone(ZoneId.of("PST")).offset,
                metadata = record.metadata
            )
            newStepsRecords.add(sr)
        }

        healthConnectClient.updateRecords(newStepsRecords)
    } catch (e: Exception) {
        // Run error handling here
    }
}

Вставить через идентификатор записи клиента

Если вы используете необязательные значения идентификатора клиентской записи и версии клиентской записи, мы рекомендуем использовать insertRecords вместо updateRecords .

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

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

suspend fun pullStepsFromDatastore() : ArrayList<StepsRecord> {
    val appStepsRecords = arrayListOf<StepsRecord>()
    // Pull data from app datastore
    // ...
    // Make changes to data if necessary
    // ...
    // Store data in appStepsRecords
    // ...
    var sr = StepsRecord(
        metadata = Metadata.autoRecorded(
            clientRecordId = "Your client record ID",
            device = Device(type = Device.TYPE_WATCH)
        ),
        // Assign more parameters for this record
    )
    appStepsRecords.add(sr)
    // ...
    return appStepsRecords
}

suspend fun upsertSteps(
    healthConnectClient: HealthConnectClient,
    newStepsRecords: ArrayList<StepsRecord>
) {
    try {
        healthConnectClient.insertRecords(newStepsRecords)
    } catch (e: Exception) {
        // Run error handling here
    }
}

После этого вы можете вызывать эти функции в основном потоке.

upsertSteps(healthConnectClient, pullStepsFromDatastore())

Проверка значения в версии клиентской записи

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

Чтобы включить версионирование данных, необходимо предоставить Metadata.clientRecordVersion Long значение, соответствующее вашей логике версионирования.

val endTime = Instant.now()
val startTime = endTime.minus(Duration.ofMinutes(15))

val stepsRecord = StepsRecord(
    count = 100L,
    startTime = startTime,
    startZoneOffset = ZoneOffset.UTC,
    endTime = endTime,
    endZoneOffset = ZoneOffset.UTC,
    metadata = Metadata.manualEntry(
        clientRecordId = "Your supplied record ID",
        clientRecordVersion = 0L, // Your supplied record version
        device = Device(type = Device.TYPE_WATCH)
    )
)

Обновления и вставки не увеличивают version автоматически при каждом изменении, предотвращая непредвиденные случаи перезаписи данных. Поэтому вам придётся вручную указать более высокое значение.

Лучшие практики записи данных

Приложения должны записывать в Health Connect только данные из собственных источников .

Если данные в вашем приложении были импортированы из другого приложения, то ответственность за запись собственных данных в Health Connect ложится на другое приложение.

Также рекомендуется реализовать логику обработки исключений при записи, таких как выход данных за пределы диапазона или внутренняя системная ошибка. Вы можете применить стратегии отсрочки и повтора к механизму планирования заданий. Если запись в Health Connect в конечном итоге не удалась, убедитесь, что ваше приложение может продолжить работу после этой точки экспорта. Не забывайте регистрировать и сообщать об ошибках для облегчения диагностики.

При отслеживании данных есть несколько рекомендаций, которым вы можете следовать в зависимости от того, как ваше приложение записывает данные.

Обработка часовых поясов

При создании записей, основанных на времени, избегайте установки смещения в zoneOffset.UTC по умолчанию, так как это может привести к неточным временным меткам, когда пользователи находятся в других часовых поясах. Вместо этого рассчитывайте смещение на основе фактического местоположения устройства. Часовой пояс устройства можно получить с помощью ZoneId.systemDefault() .

val endTime = Instant.now()
val startTime = endTime.minus(java.time.Duration.ofDays(1))
val stepsRecords = mutableListOf<StepsRecord>()
var sampleTime = startTime
val minutesBetweenSamples = 15L
while (sampleTime < endTime) {
    // Get the default ZoneId then convert it to an offset
    val zoneOffset = ZoneOffset.systemDefault().rules.getOffset(sampleTime)
    stepsRecords += StepsRecord(
        startTime = sampleTime.minus(java.time.Duration.ofMinutes(minutesBetweenSamples)),
        startZoneOffset = zoneOffset,
        endTime = sampleTime,
        endZoneOffset = zoneOffset,
        count = Random.nextLong(1, 100),
        metadata = Metadata.unknownRecordingMethod(),
    )
    sampleTime = sampleTime.plus(java.time.Duration.ofMinutes(minutesBetweenSamples))
}
healthConnectClient.insertRecords(
    stepsRecords
)

Более подробную информацию смотрите в документации по ZoneId .

Пассивное отслеживание

К ним относятся приложения, которые выполняют пассивное отслеживание фитнеса или состояния здоровья, например, непрерывно регистрируют шаги или частоту сердечных сокращений в фоновом режиме.

Вашему приложению необходимо периодически записывать данные в Health Connect следующими способами:

  • При каждой синхронизации записывайте только новые данные и обновленные данные, которые были изменены с момента последней синхронизации.
  • Запросы на фрагменты данных содержат не более 1000 записей на один запрос на запись.
  • Используйте WorkManager для планирования периодических фоновых задач с периодом времени не менее 15 минут.
  • Ограничьте запуск задач только тогда, когда устройство находится в режиме ожидания и уровень заряда батареи достаточно высок.
val constraints = Constraints.Builder()
    .requiresBatteryNotLow()
    .requiresDeviceIdle(true)
    .build()

val writeDataWork = PeriodicWorkRequestBuilder<WriteDataToHealthConnectWorker>(
        15,
        TimeUnit.MINUTES,
        5,
        TimeUnit.MINUTES
    )
    .setConstraints(constraints)
    .build()

Активное отслеживание

К ним относятся приложения, отслеживающие действия на основе событий, например, физические упражнения и сон, а также ручное управление данными, например, питанием. Эти записи создаются, когда приложение находится в активном режиме или в редких случаях, когда оно используется несколько раз в день.

Убедитесь, что ваше приложение не заставляет Health Connect работать на протяжении всего мероприятия.

Данные должны быть записаны в Health Connect одним из двух способов:

  • Синхронизируйте данные с Health Connect после завершения события. Например, синхронизируйте данные, когда пользователь завершает отслеживаемую тренировку.
  • Запланируйте одноразовую задачу с помощью WorkManager , чтобы синхронизировать данные позже.

Лучшие практики для детализации и частоты записей

При записи данных в Health Connect используйте подходящее разрешение. Правильное разрешение помогает снизить нагрузку на хранилище, сохраняя при этом согласованность и точность данных. Разрешение данных включает в себя два аспекта:

  1. Частота записи : как часто ваше приложение отправляет новые данные в Health Connect. Например, записывайте новые данные каждые 15 минут.
  2. Детализация записанных данных : частота выборки данных. Например, запись данных пульса каждые 5 секунд. Не для всех типов данных требуется одинаковая частота выборки. Обновлять данные о количестве шагов каждую секунду не так выгодно, как обновлять их реже, например, каждые 60 секунд. Однако более высокая частота выборки может предоставить пользователям более детальный и детальный обзор данных о здоровье и физической форме. Частота выборки должна обеспечивать баланс между детализацией и производительностью.

Запись данных, отслеживаемых в течение дня

Для данных, собираемых на постоянной основе, например, количества шагов, ваше приложение должно отправлять записи в Health Connect не реже, чем каждые 15 минут в течение дня.

Тип данных

Единица

Ожидал

Пример

Шаги

шаги

Каждую минуту

23:14 - 23:15 - 5 шагов

23:16 - 23:17 - 22 шага

23:17 - 23:18 - 8 шагов

ШагиКаденция

шагов/мин

Каждую минуту

23:14 - 23:15 - 5 минут в минуту

23:16 - 23:17 - 22 минуты в минуту

23:17 - 23:18 - 8 минут в минуту

Толкание инвалидной коляски

толчки

Каждую минуту

23:14 - 23:15 - 5 толчков

23:16 - 23:17 - 22 толчка

23:17 - 23:18 - 8 толчков

АктивныеСожженныеКалории

Калории

Каждые 15 минут

23:15 - 23:30 - 2 калории

23:30 - 23:45 - 25 калорий

23:45 - 00:00 - 5 калорий

Общее количество сожженных калорий

Калории

Каждые 15 минут

23:15 - 23:30 - 16 калорий

23:30 - 23:45 - 16 калорий

23:45 - 00:00 - 16 калорий

Расстояние

км/мин

Каждую минуту

23:14-23:15 - 0,008 км

23:16 - 23:16 - 0,021 км

23:17 - 23:18 - 0,012 км

Набранная высота

м

Каждую минуту

20:36 - 20:37 - 3.048м

20:39 - 20:40 - 3.048м

23:23 - 23:24 - 9.144м

Пройденные этажи

полы

Каждую минуту

23:14 - 23:15 - 5 этажей

23:16 - 23:16 - 22 этажа

23:17 - 23:18 - 8 этажей

Частота сердечных сокращений

ударов в минуту

4 раза в минуту

6:11:15 утра - 55 ударов в минуту

6:11:30 утра - 56 ударов в минуту

6:11:45 утра - 56 ударов в минуту

6:12:00 утра - 55 ударов в минуту

Вариабельность сердечного ритма (Rmssd)

РС

Каждую минуту

6:11 утра - 23 мс

Частота дыхания

вдохов/минуту

Каждую минуту

23:14 - 23:15 - 60 вдохов/минуту

23:16 - 23:16 - 62 вдоха/минуту

23:17 - 23:18 - 64 вдоха/минуту

Насыщение кислородом

%

Каждый час

6:11 - 95,208%

Писать сеансы

Данные следует записывать в Health Connect по окончании тренировки или сеанса сна.

Как минимум, ваша заявка должна соответствовать рекомендациям, указанным в столбце «Ожидаемое » в следующей таблице. По возможности следуйте наиболее оптимальным рекомендациям.

Сеансы упражнений и сна

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

Тип данных

Единица

Ожидал

Лучший

Пример

Шаги

шаги

Каждую минуту

Каждую секунду

23:14-23:15 - 5 шагов

23:16 - 23:17 - 22 шага

23:17 - 23:18 - 8 шагов

ШагиКаденция

шагов/мин

Каждую минуту

Каждую секунду

23:14-23:15 - 35 ударов в минуту

23:16 - 23:17 - 37 ударов в минуту

23:17 - 23:18 - 40 ударов в минуту

Толкание инвалидной коляски

толчки

Каждую минуту

Каждую секунду

23:14-23:15 - 5 толчков

23:16 - 23:17 - 22 толчка

23:17 - 23:18 - 8 толчков

ВелоспортПедалированиеКаденция

об/мин

Каждую минуту

Каждую секунду

23:14-23:15 - 65 об/мин

23:16 - 23:17 - 70 об/мин

23:17 - 23:18 - 68 об/мин

Власть

ватты

Каждую минуту

Каждую секунду

23:14-23:15 - 250 Вт

23:16 - 23:17 - 255 Вт

23:17 - 23:18 - 245 Вт

Скорость

км/мин

Каждую минуту

Каждую секунду

23:14-23:15 - 0,3 км/мин

23:16 - 23:17 - 0,4 км/мин

23:17 - 23:18 -0,4 км/мин

Расстояние

км/м

Каждую минуту

Каждую секунду

23:14-23:15 - 0,008 км

23:16 - 23:16 - 0,021 км

23:17 - 23:18 - 0,012 км

АктивныеСожженныеКалории

Калории

Каждую минуту

Каждую секунду

23:14-23:15 - 20 калорий

23:16 - 23:17 - 20 калорий

23:17 - 23:18 - 25 калорий

Общее количество сожженных калорий

Калории

Каждую минуту

Каждую секунду

23:14-23:15 - 36 калорий

23:16 - 23:17 - 36 калорий

23:17 - 23:18 - 41 калория

Набранная высота

м

Каждую минуту

Каждую секунду

20:36 - 20:37 - 3.048м

20:39 - 20:40 - 3.048м

23:23 - 23:24 - 9.144м

Маршруты тренировок

широта/долгота/высота

Каждые 3-5 секунд

Каждую секунду

Частота сердечных сокращений

ударов в минуту

4 раза в минуту

Каждую секунду

23:14-23:15 - 150 ударов в минуту

В следующем примере показано, как записывать данные во время или после сеанса сна:

Тип данных

Единица

Ожидаемые образцы

Пример

Стадирование сна

этап

Детализированный период времени для каждой стадии сна

23:46 - 23:50 - бодрствование

23:50 - 23:56 - легкий сон

23:56 - 00:16 - глубокий сон

Частота сердечных сокращений в состоянии покоя

ударов в минуту

Одно дневное значение (ожидается первым делом утром)

6:11 утра - 60 ударов в минуту

Насыщение кислородом

%

Одно дневное значение (ожидается первым делом утром)

6:11 - 95,208%

Мультиспортивные мероприятия

Этот подход использует существующие типы и структуры данных, а также проверяет совместимость с текущими реализациями Health Connect и считывателями данных. Этот подход широко применяется на фитнес-платформах.

В следующем примере показано, как записать данные для триатлона:

val swimStartTime = Instant.parse("2024-08-22T08:00:00Z")
val swimEndTime = Instant.parse("2024-08-22T08:30:00Z")
val bikeStartTime = Instant.parse("2024-08-22T08:40:00Z")
val bikeEndTime = Instant.parse("2024-08-22T09:40:00Z")
val runStartTime = Instant.parse("2024-08-22T09:50:00Z")
val runEndTime = Instant.parse("2024-08-22T10:20:00Z")

val swimSession = ExerciseSessionRecord(
    startTime = swimStartTime,
    endTime = swimEndTime,
    exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_SWIMMING_OPEN_WATER,
    metadata = Metadata.autoRecorded(
      device = Device(type = Device.TYPE_WATCH)
    )
)

val bikeSession = ExerciseSessionRecord(
    startTime = bikeStartTime,
    endTime = bikeEndTime,
    exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_BIKING,
    metadata = Metadata.autoRecorded(
      device = Device(type = Device.TYPE_WATCH)
    )
)

val runSession = ExerciseSessionRecord(
    startTime = runStartTime,
    endTime = runEndTime,
    exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_RUNNING,
    metadata = Metadata.autoRecorded(
      device = Device(type = Device.TYPE_WATCH)
    )
)

healthConnectClient.insertRecords(listOf(swimSession, bikeSession, runSession))