写入数据

本指南介绍了在 Health Connect 中写入或更新数据的过程。

设置数据结构

在写入数据之前,我们需要先设置记录。对于 50 多种数据类型,每种数据类型都有其各自的结构。有关可用数据类型的更多详细信息,请参阅 Jetpack 参考

基本记录

Health Connect 中的 步数 数据类型捕获用户在读数之间采取的步数。步数代表健康、健身和健康平台之间的一种常见测量值。

以下示例显示了如何设置步数数据

val stepsRecord = StepsRecord(
    count = 120,
    startTime = START_TIME,
    endTime = END_TIME,
    startZoneOffset = START_ZONE_OFFSET,
    endZoneOffset = END_ZONE_OFFSET
)

具有计量单位的记录

Health Connect 可以存储值及其计量单位,以提供准确性。一个示例是 营养 数据类型,它非常广泛且全面。它包括各种可选的营养素字段,从总碳水化合物到维生素。每个数据点都代表作为膳食或食物的一部分可能消耗的营养素。

在此数据类型中,所有营养素都以 Mass 为单位表示,而 energyEnergy 为单位表示。

以下示例显示了如何为吃过香蕉的用户设置营养数据

val banana = NutritionRecord(
    name = "banana",
    energy = 105.0.kilocalories,
    dietaryFiber = 3.1.grams,
    potassium = 0.422.grams,
    totalCarbohydrate = 27.0.grams,
    totalFat = 0.4.grams,
    saturatedFat = 0.1.grams,
    sodium = 0.001.grams,
    sugar = 14.0.grams,
    vitaminB6 = 0.0005.grams,
    vitaminC = 0.0103.grams,
    startTime = START_TIME,
    endTime = END_TIME,
    startZoneOffset = START_ZONE_OFFSET,
    endZoneOffset = END_ZONE_OFFSET
)

具有系列数据的记录

Health Connect 可以存储一系列数据。一个示例是 心率 数据类型,它捕获在读数之间检测到的心跳样本序列。

在此数据类型中,参数 samples心率样本 列表表示。每个样本都包含一个 beatsPerMinute 值和一个 time 值。

以下示例显示了如何设置心率系列数据

val heartRateRecord = HeartRateRecord(
    startTime = START_TIME,
    startZoneOffset = START_ZONE_OFFSET,
    endTime = END_TIME,
    endZoneOffset = END_ZONE_OFFSET,
    // records 10 arbitrary data, to replace with actual data
    samples = List(10) { index ->
        HeartRateRecord.Sample(
            time = START_TIME + Duration.ofSeconds(index.toLong()),
            beatsPerMinute = 100 + index.toLong(),
        )
    }
)

写入数据

Health Connect 中的常见工作流程之一是写入数据。要添加记录,请使用 insertRecords

以下示例显示了如何通过插入步数来写入数据

suspend fun insertSteps(healthConnectClient: HealthConnectClient) {
    try {
        val stepsRecord = StepsRecord(
            count = 120,
            startTime = START_TIME,
            endTime = END_TIME,
            startZoneOffset = START_ZONE_OFFSET,
            endZoneOffset = END_ZONE_OFFSET
        )
        healthConnectClient.insertRecords(listOf(stepsRecord))
    } catch (e: Exception) {
        // Run error handling here
    }
}

更新数据

如果您需要更改一个或多个记录,尤其是在您需要 同步 应用数据存储与 Health Connect 中的数据时,您可以更新您的数据。有两种方法可以更新现有数据,这取决于用于查找记录的标识符。

元数据

首先值得检查一下Metadata 类,因为更新数据时需要用到它。在创建时,Health Connect 中的每个Record 都有一个metadata 字段。以下属性与同步相关。

属性 描述
id Health Connect 中的每个Record 都有一个唯一的id 值。
Health Connect 在插入新记录时会自动填充此值。
lastModifiedTime 每个Record 还会跟踪上次修改记录的时间。
Health Connect 会自动填充此值。
clientRecordId 每个Record 可以有一个唯一的 ID 与之关联,用作您应用数据存储中的参考。
您的应用提供此值。
clientRecordVersion 如果记录具有clientRecordId,则可以使用clientRecordVersion 来确保数据与您应用数据存储中的版本保持同步。
您的应用提供此值。

通过 Record ID 更新

要更新数据,请先准备所需的记录。如有必要,对记录进行任何更改。然后,调用updateRecords 来进行更改。

以下示例展示了如何更新数据。为此,每个记录的时区偏移值都调整为 PST。

suspend fun updateSteps(
    healthConnectClient: HealthConnectClient,
    prevRecordStartTime: Instant,
    prevRecordEndTime: Instant
) {
    try {
        val request = healthConnectClient.readRecords(
            ReadRecordsRequest(
                recordType = StepsRecord::class,
                timeRangeFilter = TimeRangeFilter.between(
                    prevRecordStartTime,
                    prevRecordEndTime
                )
            )
        )

        val newStepsRecords = arrayListOf<StepsRecord>()
        for (record in request.records) {
            // Adjusted both offset values to reflect changes
            val sr = StepsRecord(
                count = record.count,
                startTime = record.startTime,
                startZoneOffset = record.startTime.atZone(ZoneId.of("PST")).offset,
                endTime = record.endTime,
                endZoneOffset = record.endTime.atZone(ZoneId.of("PST")).offset,
                metadata = record.metadata
            )
            newStepsRecords.add(sr)
        }

        client.updateRecords(newStepsRecords)
    } catch (e: Exception) {
        // Run error handling here
    }
}

通过 Client Record ID 执行 Upsert

如果您使用可选的 Client Record ID 和 Client Record Version 值,建议使用insertRecords 而不是updateRecords

insertRecords 函数能够执行 Upsert 操作。如果数据根据给定的 Client Record ID 集存在于 Health Connect 中,则会覆盖它。否则,它将作为新数据写入。当您需要同步 应用数据存储中的数据到 Health Connect 时,此方案非常有用。

以下示例展示了如何对从应用数据存储中提取的数据执行 Upsert 操作。

suspend fun pullStepsFromDatastore() : ArrayList<StepsRecord> {
    val appStepsRecords = arrayListOf<StepsRecord>()
    // Pull data from app datastore
    // ...
    // Make changes to data if necessary
    // ...
    // Store data in appStepsRecords
    // ...
    var sr = StepsRecord(
        // Assign parameters for this record
        metadata = Metadata(
            clientRecordId = cid
        )
    )
    appStepsRecords.add(sr)
    // ...
    return appStepsRecords
}

suspend fun upsertSteps(
    healthConnectClient: HealthConnectClient,
    newStepsRecords: ArrayList<StepsRecord>
) {
    try {
        healthConnectClient.insertRecords(newStepsRecords)
    } catch (e: Exception) {
        // Run error handling here
    }
}

之后,您可以在主线程中调用这些函数。

upsertSteps(healthConnectClient, pullStepsFromDatastore())

Client Record Version 中的值检查

如果您的 Upsert 数据流程包含 Client Record Version,Health Connect 会在clientRecordVersion 值中执行比较检查。如果插入数据的版本高于现有数据的版本,则会执行 Upsert 操作。否则,流程会忽略更改,并且值保持不变。

要在数据中包含版本控制,您需要使用基于版本控制逻辑的Long 值为Metadata.clientRecordVersion 提供值。

val sr = StepsRecord(
    count = count,
    startTime = startTime,
    startZoneOffset = startZoneOffset,
    endTime = endTime,
    endZoneOffset = endZoneOffset,
    metadata = Metadata(
        clientRecordId = cid,
        clientRecordVersion = version
    )
)

Upsert 操作不会在每次更改时自动递增version,从而防止意外覆盖数据的情况发生。因此,您必须手动为其提供更高的值。

数据写入最佳实践

应用只能将自身来源的数据写入 Health Connect。

如果应用中的数据是从其他应用导入的,则其他应用有责任将其自身数据写入 Health Connect。

最好实现处理写入异常的逻辑,例如数据超出范围或内部系统错误。您可以在作业调度机制上应用回退和重试策略。如果最终无法写入 Health Connect,请确保您的应用能够继续执行导出操作。不要忘记记录和报告错误以帮助诊断。

在跟踪数据时,您可以根据应用写入数据的方式遵循以下几条建议。

被动跟踪

这包括执行被动健身或健康跟踪的应用,例如在后台持续记录步数或心率。

您的应用需要定期将数据写入 Health Connect,方法如下:

  • 每次同步时,仅写入自上次同步以来已修改的新数据和更新的数据。
  • 将请求分成最多 1000 条记录/写入请求。
  • 使用WorkManager 来安排定期后台任务,时间间隔至少为 15 分钟。
  • 限制任务仅在设备空闲且电池电量不低时运行。

    val constraints = Constraints.Builder()
        .requiresBatteryNotLow()
        .requiresDeviceIdle(true)
        .build()
    
    val writeDataWork = PeriodicWorkRequestBuilder<WriteDataToHealthConnectWorker>(
            15,
            TimeUnit.MINUTES,
            5,
            TimeUnit.MINUTES
        )
        .setConstraints(constraints)
        .build()
    

主动跟踪

这包括执行基于事件的跟踪的应用,例如锻炼和睡眠,或手动用户输入,例如营养。这些记录是在应用处于前台时创建的,或者在很少情况下,应用在一天中使用几次时创建。

确保您的应用不会在整个事件期间持续运行 Health Connect。

数据必须以以下两种方式之一写入 Health Connect:

  • 在事件完成后将数据同步到 Health Connect。例如,在用户结束跟踪的锻炼会话时同步数据。
  • 使用WorkManager 安排一次性任务以稍后同步数据。

写入粒度和频率的最佳实践

将数据写入 Health Connect 时,请使用合适的解析度。使用合适的解析度有助于减少存储负载,同时仍能保持数据的一致性和准确性。数据解析度包含两方面内容:

  1. 写入频率:您的应用推送任何新数据到 Health Connect 的频率。例如,每 15 分钟写入一次新数据。
  2. 写入数据的粒度:推送的数据采样的频率。例如,每 5 秒写入一次心率样本。并非每种数据类型都需要相同的采样率。与每秒更新步数数据相比,每 60 秒更新一次步数数据的好处不大。但是,更高的采样率可以让用户更详细、更细致地了解其健康和健身数据。采样率频率应在细节和性能之间取得平衡。

写入全天监控的数据

对于持续收集的数据(如步数),您的应用应至少每天通过 Health Connect 写入一次数据,时间间隔为 15 分钟。

数据类型

单位

预期

示例

步数

每 1 分钟

23:14 - 23:15 - 5 步

23:16 - 23:17 - 22 步

23:17 - 23:18 - 8 步

步频

步/分钟

每 1 分钟

23:14 - 23:15 - 5 步/分钟

23:16 - 23:17 - 22 步/分钟

23:17 - 23:18 - 8 步/分钟

轮椅推数

每 1 分钟

23:14 - 23:15 - 5 次

23:16 - 23:17 - 22 次

23:17 - 23:18 - 8 次

主动消耗卡路里

卡路里

每 15 分钟

23:15 - 23:30 - 2 卡路里

23:30 - 23:45 - 25 卡路里

23:45 - 00:00 - 5 卡路里

总消耗卡路里

卡路里

每 15 分钟

23:15 - 23:30 - 16 卡路里

23:30 - 23:45 - 16 卡路里

23:45 - 00:00 - 16 卡路里

距离

公里/分钟

每 1 分钟

23:14-23:15 - 0.008 公里

23:16 - 23:16 - 0.021 公里

23:17 - 23:18 - 0.012 公里

海拔升高

每 1 分钟

20:36 - 20:37 - 3.048 米

20:39 - 20:40 - 3.048 米

23:23 - 23:24 - 9.144 米

攀爬楼层

每 1 分钟

23:14 - 23:15 - 5 层

23:16 - 23:16 - 22 层

23:17 - 23:18 - 8 层

心率

次/分钟

每 1 分钟

早上 6:11 - 55 次/分钟

心率变异性 RMSSD

毫秒

每 1 分钟

早上 6:11 - 23 毫秒

呼吸频率

次/分钟

每 1 分钟

23:14 - 23:15 - 60 次/分钟

23:16 - 23:16 - 62 次/分钟

23:17 - 23:18 - 64 次/分钟

血氧饱和度

%

每 1 小时

6:11 - 95.208%

写入会话

数据应在锻炼或睡眠会话结束时写入 Health Connect。

最佳实践是,任何睡眠会话或锻炼会话都应使用录制设备和相应的元数据(包括RecordingMethod)一起写入。

至少,您的应用应遵循下面“预期”列中的指南。如果可能,请遵循“最佳”指南。

在锻炼期间跟踪的数据

数据类型

单位

预期

最佳

示例

步数

每 1 分钟

每 1 秒

23:14-23:15 - 5 步

23:16 - 23:17 - 22 步

23:17 - 23:18 - 8 步

步频

步/分钟

每 1 分钟

每 1 秒

23:14-23:15 - 35 步/分钟

23:16 - 23:17 - 37 步/分钟

23:17 - 23:18 - 40 步/分钟

轮椅推数

每 1 分钟

每 1 秒

23:14-23:15 - 5 次

23:16 - 23:17 - 22 次

23:17 - 23:18 - 8 次

自行车踏频

转/分钟

每 1 分钟

每 1 秒

23:14-23:15 - 65 转/分钟

23:16 - 23:17 - 70 转/分钟

23:17 - 23:18 - 68 转/分钟

功率

瓦特

每 1 分钟

每 1 秒

23:14-23:15 - 250 瓦特

23:16 - 23:17 - 255 瓦特

23:17 - 23:18 - 245 瓦特

速度

公里/分钟

每 1 分钟

每 1 秒

23:14-23:15 - 0.3 公里/分钟

23:16 - 23:17 - 0.4 公里/分钟

23:17 - 23:18 -0.4 公里/分钟

距离

公里/米

每 1 分钟

每 1 秒

23:14-23:15 - 0.008 公里

23:16 - 23:16 - 0.021 公里

23:17 - 23:18 - 0.012 公里

主动消耗卡路里

卡路里

每 1 分钟

每 1 秒

23:14-23:15 - 20 卡路里

23:16 - 23:17 - 20 卡路里

23:17 - 23:18 - 25 卡路里

总消耗卡路里

卡路里

每 1 分钟

每 1 秒

23:14-23:15 - 36 卡路里

23:16 - 23:17 - 36 卡路里

23:17 - 23:18 - 41 卡路里

海拔升高

每 1 分钟

每 1 秒

20:36 - 20:37 - 3.048 米

20:39 - 20:40 - 3.048 米

23:23 - 23:24 - 9.144 米

锻炼路线

纬度/经度/高度

每 3-5 秒

每 1 秒

心率

次/分钟

每 1 分钟

每 1 秒

23:14-23:15 - 150 次/分钟

23:16 - 23:17 -152 次/分钟

23:17 - 23:18 - 155 次/分钟

在睡眠期间跟踪的数据

数据类型

单位

预期样本

示例

睡眠分期

阶段

每个睡眠阶段的细粒度时间段

23:46 - 23:50 - 清醒

23:50 - 23:56 - 浅睡

23:56 - 00:16 - 深睡

静息心率

次/分钟

单个每日值(预期在早上第一时间获取)

早上 6:11 - 60 次/分钟

血氧饱和度

%

单个每日值(预期在早上第一时间获取)

6:11 - 95.208%