Чтение необработанных данных

В примере ниже показано, как читать необработанные данные в рамках стандартного рабочего процесса.

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

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

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

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

Тип данных "Шаги" в Здоровье и спорт позволяет отслеживать количество шагов, пройденных пользователем между измерениями. Количество шагов – это распространенный показатель, используемый на платформах для отслеживания здоровья, физической формы и самочувствия. Платформа "Здоровье и спорт" позволяет читать и записывать данные о количестве шагов.

Чтобы читать записи, создайте ReadRecordsRequest и укажите его при вызове readRecords.

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

val response = healthConnectClient.readRecords(
    ReadRecordsRequest(
        HeartRateRecord::class,
        timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
    )
)
response.records.forEach { record ->
    /* Process records */
}

Вы также можете просматривать агрегированные данные с помощью aggregate.

suspend fun readStepsAggregate(startTime: Instant, endTime: Instant): Long {
    val response = healthConnectClient.aggregate(
        AggregateRequest(
            metrics = setOf(StepsRecord.COUNT_TOTAL),
            timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
        )
    )
    return response[StepsRecord.COUNT_TOTAL] ?: 0L
}

Чтение данных о шагах на мобильном устройстве

В Android 14 (уровень API 34) и SDK Extension версии 20 или более поздней в приложении "Здоровье и спорт" есть функция подсчета шагов на устройстве. Если приложению предоставлено разрешение READ_STEPS, сервис "Здоровье и спорт" начинает собирать данные о шагах с устройства Android, и пользователи видят, как эти данные автоматически добавляются в записи Шаги.

Чтобы проверить, доступен ли на устройстве подсчет шагов, убедитесь, что на нем установлена ОС Android 14 (уровень API 34) и как минимум версия 20 расширения SDK:

val isStepTrackingAvailable =
    Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE &&
        SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 20

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

Изменение атрибуции для шагов на устройстве

Начиная с обновления за июнь 2026 года шаги, отслеживаемые Health Connect, будут приписываться синтетическому названию пакета (SPN), например com.android.healthconnect.phone.jd5bdd37e1a8d3667a05d0abebfc4a89e.

Ранее встроенные шаги были связаны с названием пакета android. В статистике, собранной до июня 2026 г., сохранится название пакета android.

Имена субъектов-служб относятся к определенному устройству и приложению, чтобы защитить конфиденциальность пользователей:

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

Как узнать количество шагов на устройстве

Поскольку имена субъектов-служб относятся к определенным устройствам и областям, нельзя жестко задавать их значения. Вместо этого используйте API getCurrentDeviceDataSource, чтобы получить имя субъекта-службы для текущего устройства.

Для подсчета шагов на устройстве требуется расширение SDK версии 20 или выше, а API getCurrentDeviceDataSource() доступен в Android 14 (уровень API 34) с расширением SDK версии 22 или выше. Чтобы использовать этот API, задайте для параметра compileSdkExtension значение 22 или выше в файле build.gradle.kts или build.gradle на уровне модуля:

Kotlin

android {
    compileSdk = 35
    compileSdkExtension = 22
}

Яркий

android {
    compileSdk 35
    compileSdkExtension 22
}

API getCurrentDeviceDataSource() пока недоступен в библиотеке Health Connect Jetpack. В приведенных ниже примерах используется API фреймворка Android, для которого требуется обратный вызов Executor и OutcomeReceiver:

import android.content.Context
import android.health.connect.DeviceDataSource
import android.health.connect.HealthConnectException
import android.health.connect.HealthConnectManager
import android.os.OutcomeReceiver

val healthConnectManager = context.getSystemService(HealthConnectManager::class.java)
healthConnectManager?.getCurrentDeviceDataSource(
    context.mainExecutor,
    object : OutcomeReceiver<DeviceDataSource, HealthConnectException> {
        override fun onResult(result: DeviceDataSource) {
            val currentDeviceSpn = result.deviceDataOrigin.packageName
        }

        override fun onError(error: HealthConnectException) {
            // Handle error
        }
    }
)

Если приложению нужно считывать данные о шагах на устройстве или если оно показывает данные о шагах, разбитые по исходному приложению или устройству, необходимо запросить записи, в которых DataOrigin – это android или соответствует SPN устройства. Если в вашем приложении показывается атрибуция данных о шагах, используйте metadata.device, чтобы определить исходное устройство для отдельных записей. Для шагов на устройстве, которые в агрегированных данных определяются по имени поставщика услуг, можно использовать метаданные устройства, например model или manufacturer из DeviceDataSource, для атрибуции или использовать общее название, например "Ваш телефон".

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

import android.content.Context
import android.health.connect.DeviceDataSource
import android.health.connect.HealthConnectException
import android.health.connect.HealthConnectManager
import android.os.Build
import android.os.OutcomeReceiver
import android.os.ext.SdkExtensions
import androidx.health.connect.client.HealthConnectClient
import androidx.health.connect.client.records.StepsRecord
import androidx.health.connect.client.records.metadata.DataOrigin
import androidx.health.connect.client.request.AggregateRequest
import androidx.health.connect.client.time.TimeRangeFilter
import java.time.Instant
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlinx.coroutines.suspendCancellableCoroutine

suspend fun readDeviceStepsByTimeRange(
    healthConnectClient: HealthConnectClient,
    context: Context,
    startTime: Instant,
    endTime: Instant
) {
    // 1. Check if SDK Extension 22+ is available for getCurrentDeviceDataSource()
    val isDataSourceApiAvailable =
        Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE &&
            SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 22

    try {
        val healthConnectManager = context.getSystemService(HealthConnectManager::class.java)

        // 2. Safely fetch the package name only if the API is available
        val currentDeviceSpn = if (isDataSourceApiAvailable && healthConnectManager != null) {
            suspendCancellableCoroutine { continuation ->
                healthConnectManager.getCurrentDeviceDataSource(
                    context.mainExecutor,
                    object : OutcomeReceiver<DeviceDataSource, HealthConnectException> {
                        override fun onResult(result: DeviceDataSource) {
                            continuation.resume(result.deviceDataOrigin.packageName)
                        }

                        override fun onError(error: HealthConnectException) {
                            continuation.resumeWithException(error)
                        }
                    }
                )
            }
        } else {
            null
        }

        val dataOriginFilters = mutableSetOf(DataOrigin("android"))

        // 3. Explicit null-safety check using .let
        currentDeviceSpn?.let {
            dataOriginFilters.add(DataOrigin(it))
        }

        val response = healthConnectClient.aggregate(
            AggregateRequest(
                metrics = setOf(StepsRecord.COUNT_TOTAL),
                timeRangeFilter = TimeRangeFilter.between(startTime, endTime),
                dataOriginFilter = dataOriginFilters
            )
        )

        val stepCount = response[StepsRecord.COUNT_TOTAL]

    } catch (e: Exception) {
        // Now this catch block only handles actual runtime exceptions,
        // rather than Errors from missing methods.
    }
}

Отслеживание шагов на устройстве

  • Использование датчиков. Приложение "Здоровье и спорт" использует датчик TYPE_STEP_COUNTER от SensorManager. Этот датчик оптимизирован для низкого энергопотребления, поэтому идеально подходит для непрерывного отслеживания шагов в фоновом режиме.
  • Детализация данных. Чтобы экономить заряд батареи, данные о шагах обычно записываются в базу данных платформы "Здоровье и спорт" не чаще одного раза в минуту.
  • Атрибуция. Шаги, записанные этой функцией до июня 2026 г., относятся к названию пакета android в DataOrigin. После этой даты они будут приписываться к SPN, относящемуся к определенному устройству. Подробнее об изменениях в атрибуции для действий на устройстве…
  • Активация. Встроенный механизм подсчета шагов активен, только если хотя бы одному приложению на устройстве предоставлено разрешение READ_STEPS в сервисе "Здоровье и спорт".

Пример чтения в фоновом режиме

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

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

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

class ScheduleWorker(appContext: Context, workerParams: WorkerParameters) :
    CoroutineWorker(appContext, workerParams) {

    override suspend fun doWork(): Result {
        val healthConnectClient = HealthConnectClient.getOrCreate(applicationContext)
        // Perform background read logic here
        return Result.success()
    }
}
fun enqueueBackgroundReadWorker(context: Context, healthConnectClient: HealthConnectClient) {
    if (healthConnectClient
            .features
            .getFeatureStatus(
                HealthConnectFeatures.FEATURE_READ_HEALTH_DATA_IN_BACKGROUND
            ) == HealthConnectFeatures.FEATURE_STATUS_AVAILABLE
    ) {

        val periodicWorkRequest = PeriodicWorkRequestBuilder<ScheduleWorker>(1, TimeUnit.HOURS)
            .build()

        WorkManager.getInstance(context).enqueueUniquePeriodicWork(
            "read_health_connect",
            ExistingPeriodicWorkPolicy.KEEP,
            periodicWorkRequest
        )
    }
}

Параметр ReadRecordsRequest имеет значение по умолчанию pageSize, равное 1000. Если количество записей в одном файле readResponse превышает pageSize запроса, вам нужно перебрать все страницы ответа, чтобы получить все записи, используя pageToken. Однако будьте осторожны, чтобы не превысить лимит запросов.

Пример чтения pageToken

Рекомендуется использовать pageToken для чтения записей, чтобы получить все доступные данные за запрошенный период времени.

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

val type = HeartRateRecord::class
val endTime = Instant.now()
val startTime = endTime.minus(Duration.ofDays(7))

try {
    var pageToken: String? = null
    do {
        val readResponse =
            healthConnectClient.readRecords(
                ReadRecordsRequest(
                    recordType = type,
                    timeRangeFilter = TimeRangeFilter.between(
                        startTime,
                        endTime
                    ),
                    pageToken = pageToken
                )
            )
        val records = readResponse.records
        // Do something with records
        pageToken = readResponse.pageToken
    } while (pageToken != null)
} catch (quotaError: IllegalStateException) {
    // Backoff
}
Рекомендации по работе с большими наборами данных приведены в разделе Как избежать ограничения частоты запросов.

Чтение ранее записанных данных

Если приложение ранее записывало данные в Health Connect, оно может читать историю. Это необходимо, если приложению нужно повторно синхронизироваться с сервисом "Здоровье и спорт" после переустановки.

Действуют следующие ограничения на чтение:

  • На устройстве с ОС Android 14 или более новых версий:

    • Приложение может читать свои данные без ограничений по времени.
    • 30-дневное ограничение на чтение приложением других данных.
  • Для устройств с ОС Android 13 и более ранних версий

    • 30-дневный лимит на чтение приложением любых данных.

Чтобы снять ограничения, запросите разрешение на чтение.

Чтобы получить доступ к историческим данным, укажите название пакета как объект DataOrigin в параметре dataOriginFilter объекта ReadRecordsRequest.

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

try {
    val response =  healthConnectClient.readRecords(
        ReadRecordsRequest(
            recordType = HeartRateRecord::class,
            timeRangeFilter = TimeRangeFilter.between(startTime, endTime),
            dataOriginFilter = setOf(DataOrigin("com.my.package.name"))
        )
    )
    for (record in response.records) {
        // Process each record
    }
} catch (e: Exception) {
    // Run error handling here
}

Чтение уникального идентификатора устройства (UDI)

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

Чтение данных, которые хранятся более 30 дней

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

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

История разрешений для удаленного приложения

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

Предположим, пользователь удалил ваше приложение 10 мая 2023 г., а затем установил его снова 15 мая 2023 г. и предоставил разрешение на чтение. Теперь по умолчанию приложение может считывать данные начиная с 15 апреля 2023 г..

Как обрабатывать исключения

При возникновении проблем сервис "Здоровье и спорт" выдает стандартные исключения для операций CRUD. Ваше приложение должно перехватывать и обрабатывать каждое из этих исключений соответствующим образом.

В описании каждого метода на странице HealthConnectClient перечислены исключения, которые могут быть сгенерированы. Как правило, приложение должно обрабатывать следующие исключения:

Таблица 1. Исключения для платформы "Здоровье и спорт" и рекомендуемые методы
Исключение Описание Рекомендации
IllegalStateException Произошло одно из следующих событий:

  • Сервис Здоровье и спорт недоступен.
  • Запрос недействителен. Например, агрегированный запрос в периодических сегментах, где объект Instant используется для timeRangeFilter.

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

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

RemoteException Произошли ошибки в основном сервисе, к которому подключается SDK, или при обмене данными с ним.

Например, ваше приложение пытается удалить запись с определенным значением uid. Однако исключение возникает после того, как приложение при проверке в базовом сервисе обнаруживает, что запись не существует.
Чтобы избежать этой проблемы, следуйте приведенным ниже рекомендациям.

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

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