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

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

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

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

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

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

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

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

Подготовка

Перед тем как реализовать функции сна:

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

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

Сеансы сна

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

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

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

Типы данных

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

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

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

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

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

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

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

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

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

Разрешения

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

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

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

Как записать сеанс

Хотя подробные данные, например о пульсе, можно записывать на протяжении всего сеанса сна, сам SleepSessionRecord должен быть записан в Health Connect только после завершения сеанса, например когда пользователь просыпается. Запись должна содержать сеанс 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, а не записывайте каждую точку отдельно.
  • Не записывайте дублирующиеся данные. Используйте идентификаторы Client-ID. При создании записей задайте значение 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 объясните, почему приложению нужен доступ к данным о здоровье. Например: "Чтобы отслеживать тенденции изменения артериального давления и предоставлять вам статистику".
  • Проверьте длительные сеансы. Отслеживайте расход заряда батареи во время сеансов, которые длятся несколько часов, чтобы убедиться, что интервал группировки и использование датчиков не разряжают устройство.
  • Сопоставьте временные метки с частотой работы датчиков. Чтобы обеспечить высокую точность данных, сопоставьте временные метки записей с фактической частотой работы датчиков.

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

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

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

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

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

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

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

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

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

Распространенные способы отладки

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