以下示例展示了如何在常见工作流中读取原始数据。
读取数据
当应用在前台和后台运行时,Health Connect 允许应用从数据存储区读取数据:
前台读取:通常,当 应用在前台运行时,您可以从 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 月的更新开始,Health
Connect 原生跟踪的步数将归因于合成软件包名称 (SPN),例如
com.android.healthconnect.phone.jd5bdd37e1a8d3667a05d0abebfc4a89e。
以前,内置步数归因于软件包名称 android。
在 2026 年 6 月之前记录的历史步数数据保留了 android 软件包名称。
SPN 是特定于设备的,并且按应用进行限定,以保护用户隐私:
- 稳定: 当前设备的 SPN 对于您的应用是稳定的。
- 应用限定: 同一设备上的不同应用会看到不同的设备端步数数据 SPN。
查询设备端步数
由于 SPN 是限定范围的且特定于设备,因此您不得 对 SPN 值进行硬编码。请改用 getCurrentDeviceDataSource() API 来检索当前设备的 SPN。
虽然设备端步数统计功能需要 SDK 扩展版本 20 或更高版本,但 getCurrentDeviceDataSource() API 在搭载 SDK 扩展版本 11 或更高版本的 Android 14(API 级别 34)上可用。
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
如果您的应用需要读取设备端步数,或者显示按来源应用或设备细分的步数数据,则必须查询 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.
}
}
设备端步数统计
- 传感器使用情况:健康数据共享 使用
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() } }
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 写入了记录,那么该应用可以读取历史数据。这适用于以下场景:在用户重新安装应用后,应用需要与 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 天的 Health Connect 数据。
如果您需要将读取权限扩展到任何
默认限制之外,请请求
PERMISSION_READ_HEALTH_DATA_HISTORY。
否则,如果没有此权限,尝试读取超过 30 天的记录会导致错误。
已删除应用的权限历史记录
如果用户删除您的应用,所有权限(包括历史记录权限)都会被撤消。如果用户重新安装您的应用并再次授予权限, 则适用相同的 默认限制,并且您的 应用可以读取从该新日期最多回推 30 天的 Health Connect 数据。
例如,假设用户在 2023 年 5 月 10 日删除了您的应用,然后在 2023 年 5 月 15 日重新安装了该应用,并授予了读取权限。现在,您的应用默认可读取的数据日期最早为 2023 年 4 月 15 日 。
处理异常
遇到问题时,健康数据共享 会针对 CRUD 操作抛出标准异常。您的应用应酌情捕获和处理每个异常。
HealthConnectClient 中的每种方法都会列出可能会被抛出的异常。一般而言,您的应用应处理以下异常:
| 异常 | 说明 | 建议的最佳实践 |
|---|---|---|
IllegalStateException
| 您可能遇到了以下情况之一:
| 在发出请求之前,先处理可能存在的输入问题。最好能为变量赋值或在自定义函数中将它们用作参数,而不是直接在请求中使用这些值,以便顺利应用错误处理策略。 |
IOException
| 从磁盘中读取和向其中写入数据时遇到问题。 | 为避免此类问题,请参考以下建议:
|
RemoteException
| SDK 所关联的底层服务内部或与该服务通信时出现错误。 例如,您的应用尝试删除具有给定 uid 的记录。不过,当应用在检查底层服务后发现记录不存在时,会抛出异常。
| 为避免此类问题,请参考以下建议:
|
SecurityException
| 相应请求需要用到未被授予的权限时遇到问题。 | 为避免此类问题,请确保您已为已发布的健康数据共享应用声明使用健康数据共享数据类型。此外,您必须在清单文件和您的 Activity 中声明健康数据共享权限。 |