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

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

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

В 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 в сервисе "Здоровье и спорт".

Проверьте доступность платформы "Здоровье и спорт"

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

Как проверить, доступна ли платформа "Здоровье и спорт"

fun checkHealthConnectAvailability(context: Context) {
    val providerPackageName = "com.google.android.apps.healthdata" // Or get from HealthConnectClient.DEFAULT_PROVIDER_PACKAGE_NAME
    val availabilityStatus = HealthConnectClient.getSdkStatus(context, providerPackageName)

    if (availabilityStatus == HealthConnectClient.SDK_UNAVAILABLE) {
      // Health Connect is not available. Guide the user to install/enable it.
      // For example, show a dialog.
      return // early return as there is no viable integration
    }
    if (availabilityStatus == HealthConnectClient.SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED) {
      // Health Connect is available but requires an update.
      // Optionally redirect to package installer to find a provider, for example:
      val uriString = "market://details?id=$providerPackageName&url=healthconnect%3A%2F%2Fonboarding"
      context.startActivity(
        Intent(Intent.ACTION_VIEW).apply {
          setPackage("com.android.vending")
          data = Uri.parse(uriString)
          putExtra("overlay", true)
          putExtra("callerId", context.packageName)
        }
      )
      return
    }
    // Health Connect is available, obtain a HealthConnectClient instance
    val healthConnectClient = HealthConnectClient.getOrCreate(context)
    // Issue operations with healthConnectClient
}

В зависимости от статуса, возвращенного getSdkStatus(), вы можете предложить пользователю установить или обновить приложение "Здоровье и спорт" из Google Play.

Требуемые права доступа

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

  • android.permission.health.READ_STEPS
  • android.permission.health.WRITE_STEPS

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

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

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

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

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

Запрос разрешений у пользователя

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

val permissions =
    setOf(
        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.") }
    }
}
Поскольку пользователи могут в любой момент предоставить или отозвать разрешения, ваше приложение должно проверять их каждый раз перед использованием и обрабатывать ситуации, когда разрешение теряется.

Информация, содержащаяся в записи о шагах

Каждый элемент StepsRecord содержит следующую информацию:

  • count – количество шагов, сделанных за определенный период времени, в формате Long.
  • startTime – время начала интервала измерения.
  • endTime – время окончания интервала измерения.
  • startZoneOffset – смещение часового пояса для времени начала.
  • endZoneOffset – смещение часового пояса для времени окончания.

Поддерживаемые агрегации

Для параметра StepsRecord доступны следующие агрегированные значения:

Для параметра StepsCadenceRecord доступны следующие агрегированные значения:

Примеры использования

В следующих разделах рассказывается, как читать и записывать данные StepsRecord.

Запись данных о шагах

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

val zoneOffset = ZoneOffset.systemDefault().rules.getOffset(startTime)
val stepsRecord = StepsRecord(
    count = 120,
    startTime = startTime,
    endTime = endTime,
    startZoneOffset = zoneOffset,
    endZoneOffset = zoneOffset,
    metadata = Metadata.autoRecorded(
        device = Device(type = Device.TYPE_WATCH)
    )
)
healthConnectClient.insertRecords(listOf(stepsRecord))

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

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

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
}

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

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

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