读取原始数据

以下示例展示了如何在常见工作流中读取原始数据。

读取数据

Health Connect 允许应用在前台和后台读取数据存储区中的数据:

  • 前台读取:通常情况下,当应用在前台运行时,您可以从健康数据共享读取数据。在这些情况下,您可以考虑使用前台服务来运行此操作,以防用户或系统在读取操作期间将您的应用置于后台。

  • 后台读取:通过向用户请求额外权限,您可以在用户或系统将应用置于后台后读取数据。请参阅完整的后台读取示例。

Health Connect 中的“步数”数据类型会记录用户在两次读取数据之间完成的步数。步数是各健身和健康平台的常见测量内容。借助健康数据共享,您可以读取和写入步数数据。

如需读取记录,请创建一个 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 扩展版本 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 扩展版本 11 或更高版本。

getCurrentDeviceDataSource() API 尚未在 Health Connect Jetpack 库中提供。以下示例改用 Android 框架 API:

import android.content.Context
import android.health.connect.HealthConnectManager

val healthConnectManager = context.getSystemService(HealthConnectManager::class.java)
val deviceDataSource = healthConnectManager?.getCurrentDeviceDataSource()
val currentDeviceSpn = deviceDataSource?.deviceDataOrigin?.packageName

如果您的应用需要读取设备上的步数,或者需要显示按来源应用或设备细分的步数数据,您必须查询 DataOrigin 为 android 或 与设备的 SPN 相匹配的记录。如果您的应用显示步数数据的提供方信息,请使用 metadata.device 来识别各个记录的来源设备。对于汇总数据中由 SPN 标识的设备端步骤,您可以使用来自 DeviceDataSource 的 model 或 manufacturer 等设备元数据进行归因,也可以使用“您的手机”等通用标签来表示设备端步骤。

以下示例展示了如何通过同时过滤 android 和当前设备 SPN 来读取汇总的设备端步数数据:

import android.content.Context
import android.health.connect.HealthConnectManager
import android.os.Build
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

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

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

        // 2. Safely fetch the package name only if API is available and data exists
        val currentDeviceSpn = if (isDataSourceApiAvailable) {
            healthConnectManager?.getCurrentDeviceDataSource()?.deviceDataOrigin?.packageName
        } 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 权限时,设备端步数统计机制才会处于有效状态。

后台读取示例

如需在后台读取数据,请在清单文件中声明以下权限:

<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
}
如需了解读取大型数据集时的最佳实践,请参阅规划以避免速率限制。

读取之前写入的数据

如果某个应用之前已向健康数据共享写入了记录,那么该应用可以读取历史数据。这适用于以下场景:在用户重新安装应用后,应用需要与健康数据共享重新同步。

存在一些读取限制:

  • 对于 Android 14 及更高版本

    • 应用读取自身数据时不受历史限制。
    • 应用读取其他数据的 30 天限制。
  • 对于 Android 13 及更低版本

    • 应用读取任何数据的 30 天限制。

您可以请求读取权限来移除这些限制。

如需读取历史数据,您需要在 ReadRecordsRequest 的 dataOriginFilter 参数中,将软件包名称指示为 DataOrigin 对象。

以下示例展示了如何在读取心率记录时指示软件包名称:

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)

对于源自医疗级设备的记录,读取应用可以从设备元数据中提取设备唯一标识 (UDI) 的设备标识符 (DI) 部分。 如需有关如何处理此信息并将其与监管数据库进行映射的指南,请参阅元数据指南。

读取超过 30 天的数据

默认情况下,所有应用都可以读取在首次授予任何权限前最多 30 天的健康数据共享数据。

如果您需要将读取权限扩展到超出任何默认限制的范围,请请求 PERMISSION_READ_HEALTH_DATA_HISTORY。否则,如果没有此权限,尝试读取 30 天前的记录会导致错误。

已删除应用的权限历史记录

如果用户删除您的应用,所有权限(包括历史记录权限)都会被撤消。如果用户重新安装您的应用并再次授予权限,则适用相同的默认限制,并且您的应用可以读取从该新日期最多回推 30 天的健康数据共享数据。

例如,假设用户在 2023 年 5 月 10 日删除了您的应用,然后在 2023 年 5 月 15 日重新安装该应用并授予读取权限。现在,您的应用默认可读取的数据日期最早为 2023 年 4 月 15 日。

处理异常

遇到问题时,Health Connect 会针对 CRUD 操作抛出标准异常。您的应用应酌情捕获和处理每个异常。

HealthConnectClient 中的每种方法都会列出可能会被抛出的异常。一般而言,您的应用应处理以下异常:

表 1:健康数据共享例外情况和建议的最佳实践
异常 说明 建议的最佳实践
IllegalStateException 您可能遇到了以下情况之一:

  • Health Connect 服务不可用。
  • 相应请求的结构无效。例如,定期存储分区中的汇总请求,其中的 Instant 对象用于 timeRangeFilter。

在发出请求之前,先处理可能存在的输入问题。最好能为变量赋值或在自定义函数中将它们用作参数,而不是直接在请求中使用这些值,以便顺利应用错误处理策略。
IOException 从磁盘中读取和向其中写入数据时遇到问题。 为避免此类问题,请参考以下建议:

  • 备份所有的用户输入。
  • 能够处理在批量写入操作期间发生的任何问题。例如,确保进程越过问题并执行其余操作。
  • 应用重试和退避策略来处理请求问题。

RemoteException SDK 所关联的底层服务内部或与该服务通信时出现错误。

例如,您的应用尝试删除具有给定 uid 的记录。不过,当应用在检查底层服务后发现记录不存在时,会抛出异常。
为避免此类问题,请参考以下建议:

  • 在应用的数据存储区和 Health Connect 之间执行定期同步。
  • 应用重试和退避策略来处理请求问题。

SecurityException 相应请求需要用到未被授予的权限时遇到问题。 为避免此类问题,请确保您已为已发布的应用声明健康数据共享数据类型的使用情况。另外,您必须在清单文件和您的 activity 中声明健康数据共享权限。