Если вы хотите добавить в свое приложение функции для тренировок, с помощью платформы "Здоровье и спорт" можно:
- Запись данных о тренировках
- Запись маршрутов тренировок
- Запись показателей тренировки, таких как пульс, скорость и расстояние.
- Чтение данных о тренировках из других приложений
В этом руководстве рассказывается, как создавать функции для тренировок, и рассматриваются типы данных, выполнение в фоновом режиме, разрешения, рекомендуемые рабочие процессы и советы.
Обзор: как создать трекер тренировок
Чтобы создать приложение для отслеживания тренировок с помощью платформы "Здоровье и спорт", выполните следующие основные действия:
- Правильно реализовать разрешения на основе Health Permissions.
- Запись сеансов с помощью
ExerciseSessionRecord. - Запись данных о тренировке в течение сеанса.
- Правильно управлять фоновым выполнением, чтобы обеспечить непрерывный сбор данных.
- Чтение данных о сеансах для сводок о тренировках и анализа.
Этот процесс обеспечивает совместимость с другими приложениями, использующими платформу "Здоровье и спорт", и позволяет пользователю контролировать доступ к данным.
Подготовка
Перед тем как добавлять функции тренировок:
- Интегрируйте Health Connect, используя подходящую зависимость.
- Создайте экземпляр
HealthConnectClient. - Убедитесь, что в приложении реализованы запросы разрешений во время выполнения на основе разрешений для доступа к данным о здоровье.
- Если в вашем рабочем процессе используется GPS, настройте доступ к данным о местоположении и активную службу.
Основные понятия
Платформа "Здоровье и спорт" представляет данные о тренировках с помощью нескольких основных компонентов. Объект ExerciseSessionRecord содержит основную информацию о тренировке, например время начала и окончания, а также тип упражнений. Во время сеанса могут записываться различные типы данных, например HeartRateRecord или SpeedRecord. Для тренировок на улице ExerciseRoute сохраняет данные GPS, которые связаны с соответствующим сеансом.
Тренировки
ExerciseSessionRecord – это центральная запись данных о тренировке, представляющая собой одну тренировку. В каждой записи хранится следующая информация:
startTimeendTimeexerciseType- Необязательные метаданные сеанса (заголовок, заметки)
ExerciseSessionRecord также может содержать данные о маршрутах тренировок, кругах и сегментах. Кроме того, во время сеанса могут быть записаны и связаны с ним другие типы данных, например HeartRateRecord или SpeedRecord.
Связанные типы данных
Данные, связанные с тренировками, представлены отдельными типами записей. Вот некоторые из них:
HeartRateRecord– серия измерений пульса.SpeedRecord– серия измерений скорости.DistanceRecord– расстояние между показаниями.TotalCaloriesBurnedRecord– общее количество сожженных калорий между измерениями.ElevationGainedRecord– представляет собой разницу высот между показаниями.StepsCadenceRecord– каденс шагов между измерениями.PowerRecord– мощность между показателями, часто используется в таких видах активности, как езда на велосипеде.
Полный список типов данных можно найти в статье Типы данных в приложении "Здоровье и спорт".
Маршруты тренировок
Вы можете связать маршрут с тренировкой на улице, используя ExerciseRoute. Маршруты состоят из последовательных объектов ExerciseRoute.Location, каждый из которых содержит:
- Широта и долгота
- Необязательная высота
- Необязательный азимут
- Информация о точности
- Временная метка
Как связать маршруты сеансов
Объект ExerciseRoute содержит последовательные данные о местоположении во время тренировки. Она не рассматривается как отдельная запись в приложении "Здоровье и спорт". Вместо этого вы предоставляете данные ExerciseRoute при вставке или обновлении ExerciseSessionRecord.
Рекомендации по разработке
Приложения для отслеживания тренировок часто должны работать в течение длительного времени, в том числе в фоновом режиме, когда экран выключен. При создании функций для тренировок важно учитывать, как управлять фоновым выполнением и запрашивать необходимые разрешения для доступа к данным о тренировках.
Выполнение в фоновом режиме
Приложения для тренировок часто работают с выключенным экраном. В этом случае следует использовать:
- Активные службы для геолокации и сбора данных с датчиков
WorkManagerдля отложенной записи или синхронизации- Стратегии пакетной записи обычных записей
Чтобы обеспечить непрерывность, используйте один и тот же идентификатор сеанса для всех записей.
Разрешения
Чтобы читать или записывать данные о тренировках, ваше приложение должно запросить соответствующие разрешения для платформы "Здоровье и спорт". Обычно для тренировок требуются разрешения на доступ к данным о занятиях, маршрутах, а также к таким показателям, как пульс и скорость. Примеры:
- Тренировки: разрешения на чтение и запись для
ExerciseSessionRecord. - Маршруты тренировок. Разрешения на чтение и запись для приложения
ExerciseRoute. - Пульс. Разрешения на чтение и запись для
HeartRateRecord. - Скорость. Разрешения на чтение и запись для
SpeedRecord. - Расстояние. Разрешения на чтение и запись для
DistanceRecord. - Калории. Разрешения на чтение и запись для
TotalCaloriesBurnedRecord. - Достигнутая высота. Разрешения на чтение и запись для
ElevationGainedRecord. - Частота шагов: разрешения на чтение и запись для
StepsCadenceRecord. - Питание: разрешения на чтение и запись для
PowerRecord. - Действия. Разрешения на чтение и запись для
StepsRecord.
В примере ниже показано, как запросить несколько разрешений для тренировки, которая включает данные о маршруте, пульсе, расстоянии, калориях, скорости и шагах:
После создания экземпляра клиента приложению необходимо запросить у пользователя разрешения. Пользователи должны иметь возможность в любой момент предоставить или отклонить разрешения. Для этого создайте набор разрешений для нужных типов данных. Убедитесь, что разрешения из набора объявлены в манифесте Android.
val permissions = setOf( HealthPermission.getReadPermission(ExerciseSessionRecord::class), HealthPermission.getWritePermission(ExerciseSessionRecord::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, чтобы запросить эти разрешения. Откроется экран с разрешениями для сервиса "Здоровье и спорт".
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.") } } }
Чтобы запросить разрешения, вызовите функцию checkPermissionsAndRun:
if (!granted.containsAll(permissions)) { // Check if required permissions are not granted, and return return emptySet() } // Permissions already granted; proceed with inserting or reading data
Если вам нужно запросить разрешения только для одного типа данных, например частоты сердечных сокращений, включите в набор разрешений только этот тип данных:
Доступ к данным о пульсе защищен следующими разрешениями:
android.permission.health.READ_HEART_RATEandroid.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>
Как реализовать сеанс тренировки
В этом разделе описан рекомендуемый порядок записи данных о тренировках.
Как начать сеанс
Чтобы создать тренировку:
- Создайте уникальный идентификатор сеанса и убедитесь, что он стабилен. Если процесс приложения завершается и перезапускается, вы должны иметь возможность возобновить использование того же идентификатора, чтобы избежать фрагментации сеансов.
- Установите
metadata.clientRecordId, чтобы избежать дублирования при повторных попытках синхронизации. - Напишите
ExerciseSessionRecordи укажите время начала. - Начните собирать данные о типе данных и GPS-координаты только после успешной инициализации записи сеанса.
Пример:
val sessionClientId = UUID.randomUUID().toString() val zoneOffset = ZoneOffset.systemDefault().rules.getOffset(startTime) val session = ExerciseSessionRecord( startTime = startTime, startZoneOffset = zoneOffset, endTime = startTime.plusSeconds(3600), endZoneOffset = zoneOffset, exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_RUNNING, metadata = Metadata.activelyRecorded(clientRecordId = sessionClientId, device = Device(type = Device.TYPE_PHONE)), ) healthConnectClient.insertRecords(listOf(session))
Запись маршрутов тренировок
Подробнее о том, как читать необработанные данные…
При записи маршрута тренировки данные следует передавать пакетами. Это означает, что вместо того, чтобы сохранять каждую точку GPS по мере ее появления, вы собираете группу точек и сохраняете их все сразу в одном вызове.
Это важно, потому что каждый раз, когда ваше приложение считывает или записывает данные на платформе "Здоровье и спорт", оно использует немного заряда батареи и вычислительной мощности.
Ниже приведен пример кода, который позволяет записывать данные пакетами.
// 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, которые произошли в тот же временной интервал.
Если вам нужно синхронизировать данные о тренировках с бэкенд-сервером или поддерживать актуальность хранилища данных приложения с помощью Здоровья и спорта, используйте журналы изменений. Это позволяет получать список добавленных, измененных или удаленных записей за определенный период времени, что эффективнее, чем отслеживать изменения вручную или постоянно считывать все данные. Подробнее о синхронизации данных с платформой "Здоровье и спорт"…
Чтение данных о сеансах
Чтобы читать сеансы тренировок, используйте 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 для фоновой синхронизации. Используйте
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" ) )
- Проверьте существующие данные. Перед синхронизацией запросите временной диапазон, чтобы узнать, есть ли в приложении записи.
- Проверяйте точность GPS. Отфильтровывайте образцы GPS с низкой точностью (например, точки с большим радиусом горизонтальной точности) перед записью в
ExerciseRoute, чтобы карта выглядела аккуратно и профессионально. - Убедитесь, что временные метки не пересекаются. Проверьте, чтобы новый сеанс не начинался до окончания предыдущего. Пересекающиеся сеансы могут привести к конфликтам на панели управления фитнесом и в сводных расчетах.
- Четко объясняйте, зачем нужны разрешения. В процессе
Permission.createIntentобъясните, почему приложению необходим доступ к данным о здоровье. Например, можно написать: "Чтобы отслеживать тенденции изменения артериального давления и предоставлять вам статистику". - Поддержка приостановки и возобновления. Убедитесь, что приложение правильно обрабатывает приостановку. Когда пользователь ставит тренировку на паузу, прекратите сбор точек маршрута и типов данных, чтобы средний темп и продолжительность оставались точными.
- Проверьте длительные сеансы. Отслеживайте расход заряда батареи во время сеансов, которые длятся несколько часов, чтобы убедиться, что интервал группировки и использование датчиков не разряжают устройство.
- Сопоставьте временные метки с частотой работы датчиков. Чтобы обеспечить высокую точность данных, сопоставьте временные метки записей с фактической частотой работы датчиков.
Тестирование
Чтобы проверить правильность данных и удобство для пользователей, следуйте этим стратегиям тестирования и ознакомьтесь с официальной документацией по тестированию основных вариантов использования.
Инструменты проверки
- Набор инструментов для работы с платформой "Здоровье и спорт". Это вспомогательное приложение, которое позволяет вручную проверять записи, удалять тестовые данные и имитировать изменения в базе данных. Это лучший способ убедиться, что ваши записи хранятся правильно.
- Модульное тестирование с помощью
FakeHealthConnectClient. Используйте библиотеку тестирования, чтобы проверить, как приложение обрабатывает крайние случаи, например отзыв разрешения или исключения API, без необходимости использовать физическое устройство.
Контрольный список по качеству
Типичная архитектура
Реализация тренировки обычно включает:
| Компонент | Управляет |
|---|---|
| Контроллер сеанса | Состояние сеанса Таймер Логика пакетной обработки Контроллеры типов данных Выборка местоположений |
| Уровень хранилища (обертка для операций с Здоровьем и спортом): | Вставить сеанс Вставить типы данных Вставить точки маршрута Читать сводки сеансов |
| Уровень интерфейса (экраны): | Продолжительность Типы данных в реальном времени Предварительный просмотр карты Расчеты времени прохождения отрезков Отслеживание GPS в реальном времени |
Устранение неполадок
| Проблема | Возможные причины | Разрешение |
|---|---|---|
| Маршрут не связан с сеансом | Несоответствие идентификатора сеанса или диапазона времени. | Убедитесь, что временной диапазон, указанный в ExerciseRoute, полностью входит в продолжительность, заданную в ExerciseSessionRecord. Если вы планируете ссылаться на сеанс позже, убедитесь, что используете одинаковые идентификаторы. Подробнее о том, как записывать маршруты тренировок… |
| Отсутствующие типы данных (например, "Пульс") | Отсутствуют разрешения на запись или неправильно настроены временные фильтры. | Убедитесь, что вы запросили разрешение на доступ к определенному типу данных и пользователь его предоставил. Убедитесь, что в ReadRecordsRequest используется TimeRangeFilter, соответствующий сеансу. Подробнее о разрешениях… |
| Не удалось записать сеанс | Перекрывающиеся временные метки. | Платформа "Здоровье и спорт" может отклонять записи, которые пересекаются с существующими данными из того же приложения. Убедитесь, что startTime нового сеанса наступает после endTime предыдущего. |
| Данные GPS не записываются | Активная служба была закрыта или неактивна. | Чтобы собирать данные при выключенном экране, необходимо использовать сервис переднего плана с атрибутом foregroundServiceType="health" или location. |
| Появляются дублирующиеся записи | Не указан атрибут "clientRecordId". |
Присвойте уникальный clientRecordId в Metadata каждой записи. Это позволяет сервису "Здоровье и спорт" удалять дубликаты, если одни и те же данные записываются дважды при повторной попытке синхронизации. Ознакомьтесь с рекомендациями. |
Распространенные способы отладки
| Проверьте статус разрешения. | Перед каждой операцией чтения или записи вызывайте функцию getPermissionStatus(). Пользователи могут в любое время отозвать разрешения в системных настройках. |
| Проверьте режим выполнения. | Если ваше приложение не собирает данные в фоновом режиме, убедитесь, что вы указали правильные разрешения в файле AndroidManifest.xml и что пользователь не перевел приложение в режим "Ограничение фоновой активности". |