写入数据

本指南将介绍在 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 单位表示,而 energy 则用 Energy 单位表示。

以下示例展示了如何为吃了一根香蕉的用户设置营养数据:

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 进行更新/插入

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

insertRecords 函数能更新/插入数据。如果数据是基于给定的一组客户端记录 ID 而存在于 Health Connect 中,则会被覆盖。否则,会被作为新数据写入。当您需要将应用数据存储区中的数据同步到 Health Connect 时,上述方案非常有用。

以下示例展示了如何对从应用数据存储区中提取的数据执行更新/插入操作:

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())

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

如果更新/插入数据的过程包含客户端记录版本,Health Connect 会对 clientRecordVersion 值执行比较检查。如果所插数据的版本高于现有数据的版本,就会发生更新/插入。否则,该过程会忽略此更改,并且值将保持不变。

如果想在数据中包含版本编号,您需要根据版本编号逻辑为 Metadata.clientRecordVersion 提供 Long 值。

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

每当发生变化时,更新/插入操作不会自动增大 version,从而防止发生意外覆盖数据的情况。因此,您必须手动为其提供更高的值。

写入数据的最佳做法

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

如果您应用中的数据是从另一应用导入的,则需由另一应用负责将其自身的数据写入 Health Connect。

此外,最好能实现用于处理写入异常(例如数据超出边界或内部系统出错)的逻辑。您可对作业调度机制应用退避和重试策略。如果最终无法向 Health Connect 写入数据,请确保应用可以越过该导出点。别忘了要记录并报告错误以帮助诊断。

跟踪数据时,您可遵循几条建议,具体取决于应用写入数据的方式。

被动跟踪

以下建议适用于执行被动健身或健康跟踪的应用,例如在后台持续记录步数或心率的应用。

您的应用需要通过以下方式定期将数据写入 Health Connect:

  • 每次同步时,仅写入自上次同步以来发生了修改的新数据和更新后的数据。
  • 对请求进行分块,且每个写入请求的记录数量不超过 1,000 条。
  • 使用 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 时,请使用适当的分辨率。使用适当的分辨率有助于减少存储负载,同时仍可保持数据的一致性和准确性。数据解析包括以下 2 个方面:

  1. 写入频率:您的应用将任何新数据推送到 Health Connect 的频率。例如,每 15 分钟写入新数据。
  2. 写入数据的粒度:推送的数据被采样的频率。例如,每 5 秒写入一次心率样本。并非所有数据类型都需要相同的采样率。每秒更新步数数据几乎没什么好处,不如降低更新频率(例如每 60 秒一次)。不过,如果采样率较高,用户就可以更详细、更精细地了解自己的健康与健身数据。采样率频率应该在细节和性能之间取得平衡。

写入数据全天受到监控

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

数据类型

单位

预期

示例

步骤

steps

每隔 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 公里

爬升高度

m

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

HeartRateVariabilityRmssd

毫秒

每隔 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)写入。

您的应用至少应遵循下方“预期”列中的指导。请尽可能遵循“最佳”指南。

在锻炼过程中追踪的数据

数据类型

单位

预期

祝好

示例

步骤

steps

每隔 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 次推

CyclingPedalingCadence

rpm

每隔 1 分钟

每 1 秒

23:14-23:15 - 65 rpm

每分钟 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 千卡

爬升高度

m

每隔 1 分钟

每 1 秒

20:36 - 20:37 - 3.048 分钟

20:39 - 20:40 - 3.048 分钟

23:23 - 23:24 - 9.144 分钟

ExerciseRoute

纬度/经度/替代

每 3-5 秒

每 1 秒

心率

bpm

每隔 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 - 深度睡眠

静息心率

bpm

单个每日值(预计在早上的第一时间)

早 6:11 - 每分心跳 60

血氧饱和度

%

单个每日值(预计在早上的第一时间)

6:11 - 95.208%