写入数据

本指南与 Health Connect 版本 1.1.0-alpha12 兼容。

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

设置数据结构

在写入数据之前,我们需要先设置记录。Health Connect 有 50 多种数据类型,每种都有各自的结构。如需详细了解可用数据类型,请参阅 Jetpack 参考

基本记录

Health Connect 中的 步数 数据类型捕获用户在两次读数之间行走的步数。步数是健康、健身和保健平台上的常见测量指标。

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

val endTime = Instant.now()
val startTime = endTime.minus(Duration.ofMinutes(15))

val stepsRecord = StepsRecord(
    count = 120,
    startTime = startTime,
    endTime = endTime,
    startZoneOffset = ZoneOffset.UTC,
    endZoneOffset = ZoneOffset.UTC,
    metadata = Metadata.autoRecorded(
        device = Device(type = Device.TYPE_WATCH)
    )
)

带测量单位的记录

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

在此数据类型中,所有营养素均以 Mass 单位表示,而 energy 则以 Energy 单位表示。

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

val endTime = Instant.now()
val startTime = endTime.minus(Duration.ofMinutes(1))

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 = startTime,
    endTime = endTime,
    startZoneOffset = ZoneOffset.UTC,
    endZoneOffset = ZoneOffset.UTC,
    metadata = Metadata.manualEntry(
        device = Device(type = Device.TYPE_PHONE)
    )
)

带序列数据的记录

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

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

以下示例展示了如何设置心率序列数据

val endTime = Instant.now()
val startTime = endTime.minus(Duration.ofMinutes(5))

val heartRateRecord = HeartRateRecord(
    startTime = startTime,
    startZoneOffset = ZoneOffset.UTC,
    endTime = endTime,
    endZoneOffset = ZoneOffset.UTC,
    // records 10 arbitrary data, to replace with actual data
    samples = List(10) { index ->
        HeartRateRecord.Sample(
            time = startTime + Duration.ofSeconds(index.toLong()),
            beatsPerMinute = 100 + index.toLong(),
        )
    },
    metadata = Metadata.autoRecorded(
        device = Device(type = Device.TYPE_WATCH)
    ))

向用户请求权限

创建客户端实例后,您的应用需要向用户请求权限。用户必须被允许随时授予或拒绝权限。

为此,请为所需的数据类型创建一组权限。请确保这些权限已在您的 Android 清单文件中声明。

// Create a set of permissions for required data types
val PERMISSIONS =
    setOf(
  HealthPermission.getReadPermission(HeartRateRecord::class),
  HealthPermission.getWritePermission(HeartRateRecord::class),
  HealthPermission.getReadPermission(StepsRecord::class),
  HealthPermission.getWritePermission(StepsRecord::class)
)

使用 getGrantedPermissions 检查您的应用是否已获得所需权限。如果未获得,请使用 createRequestPermissionResultContract 请求这些权限。这将显示 Health Connect 权限屏幕。

// Create the permissions launcher
val requestPermissionActivityContract = PermissionController.createRequestPermissionResultContract()

val requestPermissions = registerForActivityResult(requestPermissionActivityContract) { granted ->
  if (granted.containsAll(PERMISSIONS)) {
    // Permissions successfully granted
  } else {
    // Lack of required permissions
  }
}

suspend fun checkPermissionsAndRun(healthConnectClient: HealthConnectClient) {
  val granted = healthConnectClient.permissionController.getGrantedPermissions()
  if (granted.containsAll(PERMISSIONS)) {
    // Permissions already granted; proceed with inserting or reading data
  } else {
    requestPermissions.launch(PERMISSIONS)
  }
}

由于用户可以随时授予或撤消权限,因此您的应用需要定期检查已授予的权限,并处理权限丢失的情况。

写入数据

Health Connect 中常见的某个工作流程是写入数据。如需添加记录,请使用 insertRecords

以下示例展示了如何写入数据(插入步数)

suspend fun insertSteps(healthConnectClient: HealthConnectClient) {
    val endTime = Instant.now()
    val startTime = endTime.minus(Duration.ofMinutes(5))
    try {
        val stepsRecord = StepsRecord(
            count = 120,
            startTime = startTime,
            endTime = endTime,
            startZoneOffset = ZoneOffset.UTC,
            endZoneOffset = ZoneOffset.UTC,
            metadata = Metadata.autoRecorded(
                device = Device(type = Device.TYPE_WATCH)
            )
        )
        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)
        }

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

通过客户端记录 ID 执行 upsert 操作

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

insertRecords 函数能够执行 upsert 操作。如果 Health Connect 中存在基于给定客户端记录 ID 集的数据,则会被覆盖。否则,它将作为新数据写入。当您需要将应用数据存储中的数据 同步 到 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(
        metadata = Metadata.autoRecorded(
            clientRecordId = "Your client record ID",
            device = Device(type = Device.TYPE_WATCH)
        ),
        // Assign more parameters for this record
    )
    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 操作。否则,该过程将忽略更改,值保持不变。

若要在数据中包含版本控制,您需要根据自己的版本控制逻辑为 Metadata.clientRecordVersion 提供一个 Long 值。

val endTime = Instant.now()
val startTime = endTime.minus(Duration.ofMinutes(15))

val stepsRecord = StepsRecord(
    count = 100L,
    startTime = startTime,
    startZoneOffset = ZoneOffset.UTC,
    endTime = endTime,
    endZoneOffset = ZoneOffset.UTC,
    metadata = Metadata.manualEntry(
        clientRecordId = "Your supplied record ID",
        clientRecordVersion = 0L, // Your supplied record version
        device = Device(type = Device.TYPE_WATCH)
    )
)

每当有更改时,upsert 操作不会自动递增 version,这可以防止任何意外的数据覆盖实例。因此,您必须手动提供一个更高的值。

写入数据的最佳实践

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

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

最好还实现处理写入异常的逻辑,例如数据超出边界或内部系统错误。您可以在作业调度机制上应用您的退避和重试策略。如果写入 Health Connect 最终失败,请确保您的应用可以跳过该导出点。请务必记录和报告错误以协助诊断。

在追踪数据时,您可以根据应用写入数据的方式遵循一些建议。

被动追踪

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

您的应用需要定期写入数据到 Health Connect,具体方式如下:

  • 每次同步时,仅写入自上次同步以来修改过的新数据和更新数据。
  • 每次写入请求最多分块 1000 条记录。
  • 使用 WorkManager 安排定期后台任务,时间间隔至少为 15 分钟。
  • 将任务限制为仅在设备空闲且电池电量不低时运行。
```kotlin
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. 写入数据的粒度:推送到 Health Connect 的数据的采样频率。例如,每 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 楼层

心率

次/分钟

每分钟 4 次

上午 6:11:15 - 55 次/分钟

上午 6:11:30 - 56 次/分钟

上午 6:11:45 - 56 次/分钟

上午 6:12:00 - 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。

最佳实践是,任何睡眠会话或运动会话都应连同记录设备和适当的元数据一起写入。

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

运动期间追踪的数据

数据类型

单位

预期

最佳

示例

步数

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

心率

次/分钟

每分钟 4 次

每 1 秒

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

睡眠期间追踪的数据

数据类型

单位

预期样本

示例

睡眠分期

阶段

每个睡眠阶段的精细时间段

23:46 - 23:50 - 清醒

23:50 - 23:56 - 浅睡

23:56 - 00:16 - 深度睡眠

静息心率

次/分钟

每日单一值(预期为早上第一件事)

上午 6:11 - 60 次/分钟

血氧饱和度

%

每日单一值(预期为早上第一件事)

6:11 - 95.208%