跟踪步骤

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

本指南与健康数据共享版本 1.1.0-alpha12 或更高版本兼容。

读取手机步数

在 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 适用于搭载 SDK 扩展版本 22 或更高版本的 Android 14(API 级别 34)。如需使用此 API,请在模块级 build.gradle.kts 或 build.gradle 文件中将 compileSdkExtension 设置为 22 或更高级别:

Kotlin

android {
    compileSdk = 35
    compileSdkExtension = 22
}

Groovy

android {
    compileSdk 35
    compileSdkExtension 22
}

Health Connect Jetpack 库中尚未提供 getCurrentDeviceDataSource() API。以下示例使用 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"
    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 */
}