Как создавать приложения для отслеживания сна с помощью платформы "Здоровье и спорт"

Это руководство совместимо с платформой "Здоровье и спорт" версии 1.1.0 и более поздних.

Если вы хотите добавить в свое приложение функцию отслеживания сна, то с помощью платформы "Здоровье и спорт" вы можете:

  • Запись данных о сеансах сна
  • Запись данных о фазах сна
  • Запись данных о сне, таких как пульс, уровень кислорода в крови и частота дыхания
  • Читать данные о сне из других приложений.

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

Обзор: создание комплексного трекера сна

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

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

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

Подготовка

Прежде чем внедрять функции, связанные со сном:

Основные понятия

Платформа "Здоровье и спорт" представляет данные о сне с помощью нескольких основных компонентов. SleepSessionRecord – это централизованная запись о сне, содержащая такие сведения, как время начала и окончания сна, а также его фазы. Во время сеанса могут записываться различные типы данных, например HeartRateRecord или OxygenSaturationRecord.

Сеансы сна

Данные о сне представлены в виде SleepSessionRecord. В каждой записи хранится:

  • startTime
  • endTime
  • stages – список SleepSessionRecord.Stage, включая глубокий, поверхностный и быстрый сон, а также бодрствование.
  • Необязательные метаданные сеанса (название, заметки)

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

Типы данных

Во время сеанса сна обычно регистрируются следующие типы данных:

  • SleepSessionRecord: записывает продолжительность и фазы сна, включая глубокий, поверхностный, быстрый сон и пробуждение.
  • HeartRateRecord: регистрирует частоту сердечных сокращений во время сна.
  • OxygenSaturationRecord: регистрирует уровень кислорода в крови (SpO2) во время сна.
  • RespiratoryRateRecord – регистрирует частоту дыхания во время сна.

Каждый тип данных хранится в виде отдельной записи.

Рекомендации по разработке

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

Выполнение в фоновом режиме

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

  • Активные службы для сбора данных
  • WorkManager для отложенной записи или синхронизации
  • Стратегии пакетной обработки для регулярной записи подробных данных, например пульса.

Чтобы обеспечить непрерывность, используйте один и тот же идентификатор сеанса для всех записей.

Разрешения

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

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

  • android.permission.health.READ_SLEEP
  • android.permission.health.WRITE_SLEEP

Чтобы добавить в приложение возможность отслеживать сон, запросите разрешения для типа данных SleepSession.

Вот разрешение, которое нужно объявить, чтобы записывать данные о сне:

<application>
  <uses-permission
android:name="android.permission.health.WRITE_SLEEP" />
...
</application>

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

<application>
  <uses-permission
android:name="android.permission.health.READ_SLEEP" />
...
</application>

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

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

val permissions =
    setOf(
        HealthPermission.getReadPermission(SleepSessionRecord::class),
        HealthPermission.getWritePermission(SleepSessionRecord::class),
        HealthPermission.getReadPermission(HeartRateRecord::class),
        HealthPermission.getWritePermission(HeartRateRecord::class),
        HealthPermission.getReadPermission(OxygenSaturationRecord::class),
        HealthPermission.getWritePermission(OxygenSaturationRecord::class),
        HealthPermission.getReadPermission(RespiratoryRateRecord::class),
        HealthPermission.getWritePermission(RespiratoryRateRecord::class)
    )
Используйте getGrantedPermissions, чтобы проверить, есть ли у приложения необходимые разрешения. Если нет, запросите их с помощью createRequestPermissionResultContract. Откроется экран разрешений платформы "Здоровье и спорт".
val permissions = setOf(
        HealthPermission.getReadPermission(StepsRecord::class),
        HealthPermission.getWritePermission(StepsRecord::class),
        HealthPermission.getReadPermission(HeartRateRecord::class),
        HealthPermission.getWritePermission(HeartRateRecord::class)
    )

val requestPermissionsLauncher = rememberLauncherForActivityResult(
    contract = PermissionController.createRequestPermissionResultContract()
) { grantedPermissions ->
    if (grantedPermissions.containsAll(permissions)) {
        coroutineScope.launch { snackbarHostState.showSnackbar("Permissions granted!") }
    } else {
        coroutineScope.launch { snackbarHostState.showSnackbar("Permissions denied.") }
    }
}
Поскольку пользователи могут предоставлять или отзывать разрешения в любое время, ваше приложение должно проверять разрешения каждый раз перед их использованием и обрабатывать сценарии, в которых разрешение отсутствует.

Как реализовать сеанс сна

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

Чтобы сопоставить типы данных, например HeartRateRecord или OxygenSaturationRecord, с сеансом сна, запишите их с временными метками, которые попадают между startTime и endTime сеанса. Платформа "Здоровье и спорт" не использует идентификатор сеанса, чтобы связывать сеансы сна с подробными данными. Вместо этого связь подразумевается через перекрывающиеся интервалы времени. При чтении данных о сне вы можете использовать интервал времени сеанса, чтобы запрашивать связанные типы данных, как показано в разделе Чтение данных о сне.

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

Хотя подробные данные, например пульс, можно записывать на протяжении всего сеанса сна, сам объект SleepSessionRecord должен быть записан в Здоровье и спорт только после завершения сеанса, например когда пользователь просыпается. Запись должна включать время начала (startTime) и окончания (endTime) сеанса, а также список объектов SleepSessionRecord.Stage, записанных во время сеанса. SleepSessionRecord требует, чтобы endTime было позже startTime.

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

  1. Создайте уникальный идентификатор записи клиента.
  2. Когда пользователь просыпается или отслеживание сна прекращается, соберите данные обо всех фазах сна и создайте объект SleepSessionRecord.
  3. Вставьте запись с помощью insertRecords.

Пример:

val clientRecordId = UUID.randomUUID().toString()
val sessionStartTime = LocalDateTime.of(2023, 10, 30, 22, 0).toInstant(ZoneOffset.UTC)
val sessionEndTime = LocalDateTime.of(2023, 10, 31, 7, 0).toInstant(ZoneOffset.UTC)

val stages = mutableListOf<SleepSessionRecord.Stage>()
// Add recorded stages, for example:
stages.add(SleepSessionRecord.Stage(
    startTime = sessionStartTime.plusSeconds(3600),
    endTime = sessionStartTime.plusSeconds(7200),
    stage = SleepSessionRecord.STAGE_TYPE_LIGHT)
)
stages.add(SleepSessionRecord.Stage(
    startTime = sessionStartTime.plusSeconds(7200),
    endTime = sessionStartTime.plusSeconds(10800),
    stage = SleepSessionRecord.STAGE_TYPE_DEEP)
)
// ... other stages

val session = SleepSessionRecord(
    startTime = sessionStartTime,
    startZoneOffset = ZoneOffset.UTC,
    endTime = sessionEndTime,
    endZoneOffset = ZoneOffset.UTC,
    stages = stages,
    metadata = Metadata(clientRecordId = clientRecordId)
)

healthConnectClient.insertRecords(listOf(session))

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

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

Чтение данных о сеансе и связанных с ним данных

Чтобы прочитать данные о сеансах сна, используйте ReadRecordsRequest с типом записи SleepSessionRecord и фильтром по диапазону дат. Чтобы прочитать связанные данные для определенного сеанса, отправьте второй запрос для выбранного типа данных, например HeartRateRecord, отфильтровав по startTime и endTime сеанса сна.

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

suspend fun readSleepSessionsWithAssociatedData(
    healthConnectClient: HealthConnectClient,
    startTime: Instant,
    endTime: Instant
) {
    val response = healthConnectClient.readRecords(
        ReadRecordsRequest(
            recordType = SleepSessionRecord::class,
            timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
        )
    )

    for (sleepRecord in response.records) {
        // Process each session
        val stages = sleepRecord.stages
        val notes = sleepRecord.notes

        // To read specific granular data (like heart rate) that occurred during
        // this session, use the session's startTime and endTime to filter
        // the request for that data type.
        val hrResponse = healthConnectClient.readRecords(
            ReadRecordsRequest(
                recordType = HeartRateRecord::class,
                timeRangeFilter = TimeRangeFilter.between(
                    sleepRecord.startTime,
                    sleepRecord.endTime
                )
            )
        )
        for (heartRateRecord in hrResponse.records) {
            for (sample in heartRateRecord.samples) {
                val bpm = sample.beatsPerMinute
            }
        }
    }
}

Рекомендации

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

  • Часто записывайте данные во время активного отслеживания. При активном отслеживании записывайте данные по мере их поступления или с максимальным интервалом в 15 минут.
  • Используйте WorkManager для фоновой синхронизации. Используйте WorkManager для отложенной записи. Рекомендуем установить интервал в 15 минут, чтобы соблюсти баланс между актуальностью данных в реальном времени и экономией заряда батареи.
  • Пакетные запросы на запись. Не записывайте каждое событие датчика по отдельности. Разбивайте запросы на части. Платформа "Здоровье и спорт" обрабатывает до 1000 записей на запрос на запись.
  • Используйте стабильные и уникальные идентификаторы сеансов. Если сеанс редактируется или обновляется, использование того же идентификатора предотвращает его обработку как нового отдельного сеанса.
  • Используйте пакетную обработку для типов данных. Чтобы снизить нагрузку на ввод-вывод и продлить время работы от батареи, группируйте элементы данных в один вызов insertRecords, а не записывайте каждый элемент по отдельности.
  • Используйте идентификаторы клиентов, чтобы избежать дублирования данных. При создании записей задайте metadata.clientRecordId. Используется платформой "Здоровье и спорт" для идентификации уникальных записей. Если вы попытаетесь записать данные с уже существующим идентификатором clientRecordId, сервис "Здоровье и спорт" проигнорирует дубликат или обновит существующую запись, а не создаст новую. Задание metadata.clientRecordId – самый эффективный способ предотвратить дублирование при повторных попытках синхронизации или переустановке приложения.
    val record = StepsRecord(
        count = 100,
        startTime = startTime,
        endTime = endTime,
        startZoneOffset = ZoneOffset.UTC,
        endZoneOffset = ZoneOffset.UTC,
        metadata = Metadata(
            // Use a unique ID from your own database
            clientRecordId = "daily_steps_2023_10_27_user_123"
        )
    )
  • Проверьте существующие данные. Перед синхронизацией запросите диапазон дат, чтобы узнать, есть ли уже записи из вашего приложения.
  • Убедитесь, что временные метки не пересекаются. Проверьте, что новый сеанс не начинается до окончания предыдущего. Перекрывающиеся сеансы могут привести к конфликтам в панелях фитнеса и сводных расчетах.
  • Четко объясняйте, зачем вам нужны разрешения. Используйте процесс Permission.createIntent, чтобы объяснить, зачем приложению нужен доступ к данным о здоровье. Например, можно написать: "Чтобы отслеживать динамику артериального давления и предоставлять статистику".
  • Протестируйте длительные сеансы. Отслеживайте расход заряда батареи во время сеансов, длящихся несколько часов, чтобы убедиться, что интервал пакетной обработки и использование датчиков не разряжают устройство.
  • Согласуйте временные метки с частотой датчиков. Сопоставьте временные метки записей с фактической частотой датчиков, чтобы сохранить высокую точность данных.

Тестирование

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

Инструменты проверки

  • Инструменты Здоровья и спорта: Это сопутствующее приложение позволяет вручную проверять записи, удалять тестовые данные и симулировать изменения в базе данных. Это лучший способ убедиться, что ваши записи хранятся правильно.
  • Модульное тестирование с помощью FakeHealthConnectClient. Используйте библиотеку тестирования, чтобы проверить, как приложение обрабатывает пограничные случаи, например отзыв разрешений или исключения API, без физического устройства.

Контрольный список для проверки качества

Типичная архитектура

Реализация отслеживания сна обычно включает:

Компонент Управляет
Контроллер сеансов Состояние сеанса
Таймер
Логика пакетной обработки
Контроллеры типов данных
Сбор данных
Уровень репозитория (содержит операции платформы "Здоровье и спорт") Добавление сеансов
Добавление типов данных
Добавление фаз сна
Чтение сводок сеансов
Уровень интерфейса (дисплеи): Продолжительность
Типы данных в реальном времени
Визуализация фаз сна

Устранение неполадок

Проблема Возможные причины Разрешение
Отсутствуют типы данных (например, пульс). Отсутствуют разрешения на запись или неправильно настроены фильтры времени. Убедитесь, что вы запросили разрешение на доступ к определенному типу данных и пользователь его предоставил. Убедитесь, что в ReadRecordsRequest используется TimeRangeFilter, соответствующий сеансу. Подробнее о разрешениях…
Не удается записать сеанс Временные метки пересекаются. Платформа "Здоровье и спорт" может отклонять записи, которые пересекаются с существующими данными из того же приложения. Убедитесь, что startTime нового сеанса наступает после endTime предыдущего.
Во время сна данные датчиков не записывались. Активная служба была закрыта или неактивна. Чтобы собирать данные датчиков ночью, когда экран выключен, можно использовать активную службу с foregroundServiceType="health".
Появляются повторяющиеся записи Не указан атрибут clientRecordId. Укажите уникальное значение clientRecordId в поле Metadata каждой записи. Это позволяет платформе "Здоровье и спорт" удалять дубликаты, если одни и те же данные записываются дважды во время повторной попытки синхронизации. Подробнее о рекомендациях…

Общие рекомендации по отладке

Проверьте статус разрешения. Всегда вызывайте getPermissionStatus() перед попыткой чтения или записи. Пользователи могут в любой момент отозвать разрешения в системных настройках.
Проверьте режим выполнения. Если ваше приложение не собирает данные в фоновом режиме, убедитесь, что вы указали правильные разрешения в файле AndroidManifest.xml и что пользователь не перевел приложение в режим ограничения энергопотребления.