以下示例展示了如何在常见工作流中读取原始数据。
读取数据
当应用在前台和后台运行时,健康数据共享 允许应用从数据存储区读取数据:
前台读取:通常,当 应用在前台运行时,您可以从健康数据共享读取数据。在这些情况下,如果用户或系统在读取操作期间将应用置于后台,您可以考虑使用前台服务来运行此操作。
后台读取:通过向用户请求额外权限,您可以在用户或系统将应用置于后台后读取数据。请参阅完整的后台读取示例。
健康数据共享中的“步数”数据类型会记录用户在两次读取数据之间完成的步数。步数是各健身和健康平台的常见测量内容。借助健康数据共享,您可以读取和写入步数数据。
如需读取记录,请创建一个 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
如果您的应用需要读取设备端步数,或者显示按来源应用或设备细分的步数数据,则必须查询 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() } }
@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 中的每种方法都会列出可能会被抛出的异常。一般而言,您的应用应处理以下异常:
| 异常 | 说明 | 建议的最佳实践 |
|---|---|---|
IllegalStateException
| 您可能遇到了以下情况之一:
| 在发出请求之前,先处理可能存在的输入问题。最好能为变量赋值或在自定义函数中将它们用作参数,而不是直接在请求中使用这些值,以便顺利应用错误处理策略。 |
IOException
| 从磁盘中读取和向其中写入数据时遇到问题。 | 为避免此类问题,请参考以下建议:
|
RemoteException
| SDK 所关联的底层服务内部或与该服务通信时出现错误。 例如,您的应用尝试删除具有给定 uid 的记录。不过,当应用在检查底层服务后发现记录不存在时,会抛出异常。
| 为避免此类问题,请参考以下建议:
|
SecurityException
| 相应请求需要用到未被授予的权限时遇到问题。 | 为避免此类问题,请确保您已为已发布的应用 声明使用健康数据共享数据类型。另外,您必须在清单文件 和您的 Activity 中声明健康数据共享权限。 |