写入数据

本指南介绍了在 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 使数据与应用数据存储中的版本保持同步。
您的应用提供此值。

通过记录 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
    }
}

通过客户端记录 ID 执行 Upsert

如果您使用可选的客户端记录 ID 和客户端记录版本值,建议使用 insertRecords 而不是 updateRecords

insertRecords 函数能够执行 Upsert 操作。如果数据根据给定的客户端记录 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())

客户端记录版本中的值检查

如果您的 Upsert 数据过程包含客户端记录版本,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 秒更新一次的频率几乎没有好处。但是,更高的采样率可以让用户更详细、更细致地了解其健康和健身数据。采样率频率应在细节和性能之间取得平衡。

写入全天监控的数据

对于持续收集的数据(如步数),您的应用应在全天至少每 15 分钟写入一次 Health Connect。

数据类型

单位

预期

示例

步数

每 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 层

心率

bpm

每 1 分钟

早上 6:11 - 55 bpm

心率变异性 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 次

自行车踏频

rpm

每 1 分钟

每 1 秒

23:14-23:15 - 65 rpm

23:16 - 23:17 - 70 rpm

23:17 - 23:18 - 68 rpm

功率

瓦特

每 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 秒

心率

bpm

每 1 分钟

每 1 秒

23:14-23:15 - 150 bpm

23:16 - 23:17 -152 bpm

23:17 - 23:18 - 155 bpm

睡眠期间跟踪的数据

数据类型

单位

预期样本

示例

睡眠阶段

阶段

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

23:46 - 23:50 - 清醒

23:50 - 23:56 - 浅睡

23:56 - 00:16 - 深睡

静息心率

bpm

每日单次数值(预计在早上第一时间获取)

上午 6:11 - 60 bpm

血氧饱和度

%

每日单次数值(预计在早上第一时间获取)

6:11 - 95.208%