Создавайте индивидуальные программы тренировок с помощью Health Connect.

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

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

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

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

Для создания комплексной системы отслеживания тренировок с помощью Health Connect выполните следующие основные шаги:

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

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

Прежде чем начать

Перед внедрением функций тренировки:

Ключевые понятия

Health Connect представляет данные о тренировках с помощью нескольких основных компонентов. ExerciseSessionRecord служит центральной записью для тренировки, содержащей такие сведения, как время начала или окончания и тип упражнения. Во время тренировки могут записываться различные типы данных, такие как HeartRateRecord или SpeedRecord . Для тренировок на открытом воздухе ExerciseRoute сохраняет данные GPS, которые связаны с соответствующей тренировкой.

Занятия спортом

ExerciseSessionRecord — это центральная запись данных о тренировке, представляющая собой отдельную тренировочную сессию. Каждая запись хранит:

  • startTime
  • endTime
  • exerciseType
  • Дополнительные метаданные сессии (заголовок, примечания)

Объект ExerciseSessionRecord также может содержать маршруты тренировок, круги и сегменты в качестве части своих данных. Кроме того, во время тренировки могут записываться и связываться с другими типами данных, такими как HeartRateRecord или SpeedRecord .

Связанные типы данных

Данные, связанные с тренировками, представлены отдельными типами записей. К распространенным типам относятся:

  • HeartRateRecord : Представляет собой серию измерений частоты сердечных сокращений.
  • SpeedRecord : Представляет собой последовательность измерений скорости.
  • DistanceRecord : Отображает расстояние, пройденное между измерениями.
  • TotalCaloriesBurnedRecord : Отображает общее количество сожженных калорий между измерениями.
  • ElevationGainedRecord : Отражает увеличение высоты, достигнутое между измерениями.
  • StepsCadenceRecord : Отображает частоту шагов между чтениями.
  • PowerRecord : Отображает выходную мощность между измерениями, что характерно для таких видов деятельности, как езда на велосипеде.

Полный список типов данных см. в разделе «Типы данных Health Connect» .

маршруты для тренировок

С помощью ExerciseRoute можно связать маршрут с тренировками на открытом воздухе. Маршруты состоят из последовательных объектов ExerciseRoute.Location , каждый из которых содержит:

  • Широта и долгота
  • Дополнительная высота
  • Дополнительный подшипник
  • Информация о точности
  • Отметка времени

Маршруты сеансов связи

Объект ExerciseRoute содержит данные о последовательном местоположении для каждой тренировки. В Health Connect он не рассматривается как отдельная запись. Вместо этого данные ExerciseRoute предоставляются при добавлении или обновлении объекта ExerciseSessionRecord .

Вопросы развития

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

Фоновое выполнение

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

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

Обеспечьте непрерывность, сохраняя идентификатор сессии неизменным при всех операциях записи.

Разрешения

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

  • Упражнения: права на чтение и запись для ExerciseSessionRecord .
  • Маршруты упражнений: права на чтение и запись для ExerciseRoute .
  • Пульс: Права доступа на чтение и запись для HeartRateRecord .
  • Speed: Права доступа на чтение и запись для SpeedRecord .
  • Расстояние: Права доступа на чтение и запись для DistanceRecord .
  • Калории: Права на чтение и запись для TotalCaloriesBurnedRecord .
  • Полученная высота: права на чтение и запись для ElevationGainedRecord .
  • Steps Cadence: Права доступа на чтение и запись для StepsCadenceRecord .
  • Power: Права доступа на чтение и запись для PowerRecord .
  • Шаги: Предоставьте права на чтение и запись для StepsRecord .

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

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

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

// Create a set of permissions for required data types
val PERMISSIONS =
    setOf(
  HealthPermission.getReadPermission(ExerciseSessionRecord::class),
  HealthPermission.getWritePermission(ExerciseSessionRecord::class),
  HealthPermission.getReadPermission(ExerciseRoute::class),
  HealthPermission.getWritePermission(ExerciseRoute::class),
  HealthPermission.getReadPermission(HeartRateRecord::class),
  HealthPermission.getWritePermission(HeartRateRecord::class),
  HealthPermission.getReadPermission(SpeedRecord::class),
  HealthPermission.getWritePermission(SpeedRecord::class),
  HealthPermission.getReadPermission(DistanceRecord::class),
  HealthPermission.getWritePermission(DistanceRecord::class),
  HealthPermission.getReadPermission(TotalCaloriesBurnedRecord::class),
  HealthPermission.getWritePermission(TotalCaloriesBurnedRecord::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)
  }
}

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

Для запроса разрешений вызовите функцию checkPermissionsAndRun :

if (!granted.containsAll(PERMISSIONS)) {
    requestPermissions.launch(PERMISSIONS)
    // Check if required permissions are not granted, and return
  }
// Permissions already granted; proceed with inserting or reading data

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

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

  • android.permission.health.READ_HEART_RATE
  • android.permission.health.WRITE_HEART_RATE

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

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

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

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

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

Организуйте тренировку.

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

Начать сессию

Чтобы создать новую тренировку:

  1. Создайте уникальный идентификатор сессии: убедитесь, что этот идентификатор стабилен. Если процесс вашего приложения будет завершен и перезапущен, вы должны иметь возможность возобновить работу, используя тот же идентификатор, чтобы предотвратить фрагментацию сессий.
  2. Установите значение metadata.clientRecordId , чтобы предотвратить появление дубликатов при повторных попытках синхронизации.
  3. Создайте объект ExerciseSessionRecord , указав время начала.
  4. Начать сбор данных типа «Данные» и данных GPS: начинать их только после успешной инициализации записи сессии.

Пример:

val sessionId = UUID.randomUUID().toString()
val sessionClientId = UUID.randomUUID().toString()

val session = ExerciseSessionRecord(
    id = sessionId,
    exerciseType = ExerciseType.EXERCISE_TYPE_RUNNING,
    startTime = Instant.now(),
    endTime = null,
    metadata = Metadata(clientRecordId = sessionClientId),
)

healthConnectClient.insertRecords(listOf(session))

Записывайте маршруты тренировок

Чтобы узнать больше о методах чтения, см. раздел «Чтение необработанных данных» .

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

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

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

// 1. Create a list to hold your route locations
val routeLocations = mutableListOf<ExerciseRoute.Location>()

// 2. Add points to your list as the exercise happens
routeLocations.add(
    ExerciseRoute.Location(
        time = Instant.now(),
        latitude = 37.7749,
        longitude = -122.4194
    )
)

// ... keep adding points over a period of time ...

// 3. Save the whole list at once (Batching)
val session = ExerciseSessionRecord(
    startTime = startTime,
    endTime = endTime,
    exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_RUNNING,
    // We pass the whole list here
    exerciseRoute = ExerciseRoute(routeLocations)
)

healthConnectClient.insertRecords(listOf(session))

Завершить сессию

После прекращения сбора данных:

  • Обновите запись: Ваше приложение обновляет ExerciseSessionRecord , добавляя значение endTime .
  • Завершение обработки данных: При желании можно вычислить сводные значения (например, общее расстояние или средний темп) и записать их в виде дополнительных записей.
val finishedSession = session.copy(endTime = Instant.now())
healthConnectClient.updateRecords(listOf(finishedSession))

Чтение данных о тренировках

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

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

Чтение сессий

Для чтения данных о тренировках используйте запрос ReadRecordsRequest с типом ExerciseSessionRecord . Обычно фильтрация осуществляется по определенному временному диапазону.

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

    for (exerciseRecord in response.records) {
        // Process each session
        val exerciseType = exerciseRecord.exerciseType
        val notes = exerciseRecord.notes
    }
}

Читать маршруты

Хотя данные ExerciseRoute записываются как часть тренировочной сессии, их необходимо считывать отдельно. Используйте метод getExerciseRoute() с идентификатором сессии, чтобы прочитать данные маршрута:

suspend fun readExerciseRoute(
    healthConnectClient: HealthConnectClient,
    exerciseSessionRecord: ExerciseSessionRecord
) {
    // Check if the session has a route
    val route = healthConnectClient.getExerciseRoute(
        exerciseSessionRecordId = exerciseSessionRecord.metadata.id
    )

    when (route) {
        is ExerciseRouteResponse.Success -> {
            val locations = route.exerciseRoute.locations
            for (location in locations) {
                // Use latitude, longitude, and altitude
            }
        }
        is ExerciseRouteResponse.NoData -> {
            // Handle case where no route exists
        }
        is ExerciseRouteResponse.ConsentRequired -> {
            // Handle case where permissions are missing
        }
    }
}

Типы данных для чтения

Для чтения конкретных детализированных данных (например, частоты сердечных сокращений), полученных во время сеанса, используйте startTime и endTime сеанса для фильтрации запроса по этому типу данных.

suspend fun readHeartRateData(
    healthConnectClient: HealthConnectClient,
    exerciseSession: ExerciseSessionRecord
) {
    val response = healthConnectClient.readRecords(
        ReadRecordsRequest(
            recordType = HeartRateRecord::class,
            timeRangeFilter = TimeRangeFilter.between(
                exerciseSession.startTime,
                exerciseSession.endTime
            )
        )
    )

    for (heartRateRecord in response.records) {
        for (sample in heartRateRecord.samples) {
            val bpm = sample.beatsPerMinute
        }
    }
}

Передовые методы

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

  • Частота записи
    • Активное отслеживание (в фоновом режиме): для активных тренировок данные записываются по мере их поступления или с максимальным интервалом в 15 минут.
    • Фоновая синхронизация: используйте WorkManager для отложенной записи. Старайтесь устанавливать интервал в 15 минут, чтобы найти баланс между передачей данных в реальном времени и экономией заряда батареи.
    • Пакетная обработка: Не записывайте каждое событие с датчика по отдельности. Разделите запросы на части. Health Connect обрабатывает до 1000 записей за один запрос на запись.
  • Сохраняйте идентификаторы сессий стабильными и уникальными: используйте согласованные идентификаторы для ваших сессий. Если сессия редактируется или обновляется, использование того же идентификатора предотвратит ее обработку как новой, отдельной тренировки.
  • Используйте пакетную обработку как для типов данных, так и для точек маршрута: чтобы уменьшить накладные расходы на ввод/вывод и продлить срок службы батареи, группируйте точки данных в один вызов insertRecords , а не записывайте каждую точку по отдельности.
  • Избегайте записи дублирующихся данных: используйте идентификаторы клиентов. При создании записей задайте параметр metadata.clientRecordId . Health Connect использует его для идентификации уникальных записей. Если вы попытаетесь записать запись с уже существующим clientRecordId , Health Connect проигнорирует дубликат или обновит существующую запись, а не создаст новую. Установка параметра 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"
        )
    )
    
  • Проверьте существующие данные: перед синхронизацией запросите временной диапазон, чтобы узнать, существуют ли уже записи из вашего приложения.

  • Проверьте точность GPS: перед записью в ExerciseRoute отфильтруйте образцы GPS с низкой точностью (например, точки с высоким горизонтальным радиусом точности), чтобы убедиться, что карта выглядит аккуратной и профессиональной.

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

  • Четко обоснуйте необходимость предоставления разрешения: используйте поток Permission.createIntent , чтобы объяснить, почему вашему приложению необходим доступ к данным о здоровье, например: «Для построения карты ваших пробежек и расчета сжигаемых калорий».

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

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

  • Согласуйте временные метки с частотой работы датчиков: сопоставьте временные метки записей с фактической частотой работы ваших датчиков (например, 1 Гц для GPS), чтобы обеспечить высокую точность данных.

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

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

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

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

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

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

Обычно программа тренировки включает в себя:

Компонент Управляет
контроллер сессии состояние сессии
Таймер
Логика пакетной обработки
Контроллеры типов данных
Выборочный отбор по местоположению
Уровень репозитория (обертывает операции Health Connect): Вставить сессию
Вставьте типы данных
Вставляет точки маршрута
Ознакомьтесь с краткими обзорами сессий.
Слой пользовательского интерфейса (дисплеи): Продолжительность
Типы данных в реальном времени
Предварительный просмотр карты
Расчеты разделения
Отслеживание местоположения по GPS в реальном времени

Поиск неисправностей

Симптом Возможная причина Разрешение
Маршрут не связан с сессией Несоответствие идентификатора сессии или временного диапазона. Убедитесь, что ExerciseRoute задан с временным диапазоном, полностью попадающим в длительность ExerciseSessionRecord . Убедитесь, что вы используете согласованные идентификаторы при последующем обращении к сессии. См. раздел «Запись маршрутов выполнения упражнений» .
Отсутствующие типы данных (например, частота сердечных сокращений) Отсутствуют права на запись или некорректно работают временные фильтры. Убедитесь, что вы запросили разрешение на доступ к данным определенного типа, и пользователь его предоставил. Проверьте, что ваш ReadRecordsRequest использует TimeRangeFilter , соответствующий сессии. См. раздел «Разрешения» .
Сессия не записывается Перекрывающиеся временные метки. Приложение Health Connect может отклонять записи, которые частично совпадают с существующими данными из того же приложения. Убедитесь, что время startTime новой сессии позже времени endTime предыдущей.
Данные GPS не записаны. Работа основной службы была прекращена или приостановлена. Для сбора данных при выключенном экране необходимо использовать службу переднего плана с атрибутом foregroundServiceType="health" или location.
Появляются повторяющиеся записи. Отсутствует clientRecordId . Присвойте каждой записи уникальный clientRecordId в Metadata . Это позволит Health Connect выполнить дедупликацию, если одни и те же данные будут записаны дважды во время повторной попытки синхронизации. См. раздел «Рекомендации» .

Типичные шаги отладки

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