跟踪步骤

健康数据共享提供步数数据类型,用于使用 StepsRecord 记录步数。步数是健康和健身追踪的基本测量指标。

读取移动步数

在 Android 14(API 级别 34)和 SDK 扩展版本 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 年 6 月的更新要求。

设备端步数的归因变更

自 2026 年 6 月更新起,由健康数据共享原生跟踪的步数将归因于合成软件包名称 (SPN),例如 com.android.healthconnect.phone.jd5bdd37e1a8d3667a05d0abebfc4a89e。

之前,内置步数归因于软件包名称 android。2026 年 6 月之前记录的历史步数数据会保留 android 软件包名称。

SPN 针对特定设备,并按应用进行范围限定,以保护用户隐私:

  • 稳定:当前设备的 SPN 对您的应用来说是稳定的。
  • 应用级:同一设备上的不同应用会看到不同的设备端步数数据 SPN。

查询设备端步数

由于 SPN 是有范围且特定于设备的,因此您不得对 SPN 值进行硬编码。而是改用 getCurrentDeviceDataSource API 来检索当前设备的 SPN。

虽然设备端步数统计需要 SDK 扩展版本 20 或更高版本,但 getCurrentDeviceDataSource() API 在 Android 14(API 级别 34)上可用,且需要 SDK 扩展版本 22 或更高版本。如需使用此 API,请在模块级 build.gradle.kts 或 build.gradle 文件中将 compileSdkExtension 设置为 22 或更高版本:

Kotlin

android {
    compileSdk = 35
    compileSdkExtension = 22
}

Groovy

android {
    compileSdk 35
    compileSdkExtension 22
}

getCurrentDeviceDataSource() API 尚未在健康数据共享 Jetpack 库中提供。以下示例使用 Android 框架 API,该 API 需要 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 标识的设备端步骤,您可以使用来自 DeviceDataSource 的 model 或 manufacturer 等设备元数据进行归因,也可以使用“您的手机”等通用标签来表示设备端步骤。

以下示例展示了如何通过同时过滤 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.
    }
}

设备端步数统计

  • 传感器使用情况:健康数据共享使用 SensorManager 的 TYPE_STEP_COUNTER 传感器。此传感器经过优化,可降低功耗,非常适合在后台持续跟踪步数。
  • 数据粒度:为了节省电池电量,步数数据通常会分批写入健康数据共享数据库,写入频率不会超过每分钟一次。
  • 归因:此功能在 2026 年 6 月之前记录的步数归因于 DataOrigin 中的 android 软件包名称。在此日期之后,它们会归因于特定于设备的 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 实例来写入步数数据。以下示例展示了如何记录用户行走的 1,000 步:

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 */
}