You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用HealthConnect API时卡路里数据聚合结果存在差异

Health Connect API 手动求和与聚合结果不一致的解决方案

问题背景

通过Kotlin实现校验逻辑,对比过去30天内每日手动汇总的TotalCaloriesBurnedRecord卡路里值,与Health Connect aggregate接口返回的ENERGY_TOTAL聚合值,结果始终存在巨大差异。日志显示手动求和远小于聚合值,使用官方测试工具也复现了该问题。

核心原因

代码中仅调用了一次readRecords接口,未处理分页返回逻辑。当每日的卡路里记录数量超过设置的pageSize(2000)时,API会将数据分页返回,仅获取第一页记录会导致手动求和遗漏后续页的内容(比如系统自动生成的基础代谢记录、其他应用写入的批量小记录),而aggregate接口会计算所有记录的总和,因此出现数值差异。

解决方案

循环调用readRecords,直到返回的nextPageToken为null,收集所有分页的记录后再进行求和计算。

修正后的代码

suspend fun validateCaloriesData(healthConnectClient: HealthConnectClient) {
    // Time zone and date setup
    val zoneId = ZoneId.systemDefault()
    val endTime = Instant.now().atZone(zoneId).truncatedTo(ChronoUnit.DAYS)
    val startTime = endTime.minusDays(30)

    var currentDay = startTime
    while (currentDay.isBefore(endTime)) {
        val startOfDay = currentDay.toInstant()
        val endOfDay = currentDay.plusDays(1).toInstant()

        val dailyRecords = mutableListOf<Double>()

        try {
            var pageToken: String? = null
            // 循环读取所有分页的记录
            do {
                val records = healthConnectClient.readRecords(
                    ReadRecordsRequest(
                        recordType = TotalCaloriesBurnedRecord::class,
                        timeRangeFilter = TimeRangeFilter.between(startOfDay, endOfDay),
                        pageSize = 2000,
                        pageToken = pageToken,
                        ascendingOrder = true
                    )
                )
                records.records.forEach {
                    dailyRecords.add(it.energy.inKilocalories ?: 0.0)
                }
                pageToken = records.nextPageToken
            } while (pageToken != null)

            val sumOfDailyRecords = dailyRecords.sum()
            val response = healthConnectClient.aggregate(
                AggregateRequest(
                    metrics = setOf(TotalCaloriesBurnedRecord.ENERGY_TOTAL),
                    timeRangeFilter = TimeRangeFilter.between(startOfDay, endOfDay)
                )
            )
            val aggregatedCalories = response[TotalCaloriesBurnedRecord.ENERGY_TOTAL]?.inKilocalories ?: 0.0

            // 考虑浮点数精度问题,使用近似对比
            if (kotlin.math.abs(sumOfDailyRecords - aggregatedCalories) > 0.01) {
                Log.e("DailyCaloriesValidation", "Discrepancy found on day $currentDay: Manual sum is $sumOfDailyRecords but aggregated is $aggregatedCalories")
            } else {
                Log.i("DailyCaloriesValidation", "Match on day $currentDay: $sumOfDailyRecords")
            }

        } catch (e: Exception) {
            Log.e("DailyCaloriesValidation", "Error reading or aggregating calories for day $currentDay: ${e.message}")
        }

        currentDay = currentDay.plusDays(1)
    }
}

额外说明

  1. 浮点数精度处理:由于卡路里数值可能存在小数位,直接用!=对比容易出现精度误差,改用绝对值差小于0.01的近似对比更合理。
  2. 记录完整性:确保应用已申请READ_TOTAL_CALORIES_BURNED权限,否则无法读取所有来源的卡路里记录。

内容的提问来源于stack exchange,提问作者Catfoxes

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 21:44:52