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

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

Это руководство совместимо с платформой "Здоровье и спорт" версии 1.1.0-alpha12 или более поздней.

Прочитать шаги на мобильном устройстве

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

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

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 года, шаги, отслеживаемые непосредственно платформой "Здоровье и спорт", связываются с синтетическим названием пакета (SPN), например com.android.healthconnect.phone.jd5bdd37e1a8d3667a05d0abebfc4a89e.

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

Чтобы защитить конфиденциальность пользователей, SPN-номера уникальны для каждого устройства и приложения.

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

Запрос для действий на устройстве

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

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

Kotlin

android {
    compileSdk = 35
    compileSdkExtension = 22
}

Яркий

android {
    compileSdk 35
    compileSdkExtension 22
}

API getCurrentDeviceDataSource() пока недоступен в библиотеке 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, чтобы определить исходное устройство для отдельных записей. Для шагов на устройстве, идентифицированных по SPN в агрегированных данных, вы можете использовать метаданные устройства, например 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"
    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 */
}