读取原始数据

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

读取数据

当应用在前台和后台运行时,健康数据共享 允许应用从数据存储区读取数据:

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

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

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

如需读取记录,请创建一个 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 月的更新开始,由 Health Connect 原生跟踪的步数将归因于合成软件包名称 (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 或更高版本。

Health Connect Jetpack 库中尚不提供 getCurrentDeviceDataSource() API。以下示例改用 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

如果您的应用需要读取设备端步数,或者显示按来源应用或设备细分的步数数据,则必须查询 DataOriginandroid 与设备的 SPN 匹配的记录。如果您的应用显示步数数据的归因,请使用 metadata.device来标识各个记录的来源设备。对于汇总数据中由 SPN 标识的设备端步数,您可以使用来自 DeviceDataSource 的设备元数据(例如 modelmanufacturer)进行归因,也可以使用通用标签(例如“您的手机”)来表示设备端步数。

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

设备端步数统计

  • 传感器使用情况:健康数据共享 使用 TYPE_STEP_COUNTER 传感器,该传感器来自 SensorManager。此传感器经过优化,可降低功耗,非常适合持续后台步数跟踪。
  • 数据粒度:为了节省电池续航时间,步数数据通常会批量处理并写入健康数据共享数据库,频率不超过每分钟一次。
  • 归因:此功能在 2026 年 6 月之前记录的步数 归因于android 软件包名称在 DataOrigin中。在此日期之后,它们归因于设备专用的 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()
    }
}
@OptIn(ExperimentalFeatureAvailabilityApi::class)
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
}
如需了解读取大型数据集时的最佳实践,请参阅 规划以避免速率限制

读取之前写入的数据

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

您需遵守一些读取限制:

  • 对于 Android 14 及更高版本

    • 应用读取自己的数据时没有历史记录限制。
    • 应用读取其他数据时有 30 天的限制。
  • 对于 Android 13 及更低版本

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

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

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

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

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
}

读取超过 30 天的数据

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

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

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

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

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

处理异常

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

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

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

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

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

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

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

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

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

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