以下示例展示了如何在常见工作流中读取原始数据。
读取数据
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 中的每种方法都会列出可能会被抛出的异常。一般而言,您的应用应处理以下异常:
| 异常 | 说明 | 建议的最佳实践 |
|---|---|---|
IllegalStateException
| 您可能遇到了以下情况之一:
| 在发出请求之前,先处理可能存在的输入问题。最好能为变量赋值或在自定义函数中将它们用作参数,而不是直接在请求中使用这些值,以便顺利应用错误处理策略。 |
IOException
| 从磁盘中读取和向其中写入数据时遇到问题。 | 为避免此类问题,请参考以下建议:
|
RemoteException
| SDK 所关联的底层服务内部或与该服务通信时出现错误。 例如,您的应用尝试删除具有给定 uid 的记录。不过,当应用在检查底层服务后发现记录不存在时,会抛出异常。
| 为避免此类问题,请参考以下建议:
|
SecurityException
| 相应请求需要用到未被授予的权限时遇到问题。 | 为避免此类问题,请确保您已为已发布的应用声明健康数据共享数据类型的使用情况。另外,您必须在清单文件和您的 activity 中声明健康数据共享权限。 |