1. 简介
什么是 Health Connect?
Health Connect 是一个面向 Android 应用开发者的健康数据平台。它为访问用户健康和健身数据提供单一、统一的接口,并在所有设备上提供一致的功能行为。通过 Health Connect,用户可以在设备上获得安全的健康和健身数据存储,并对访问拥有完全的控制和透明度。
Health Connect 如何运作?
Health Connect 支持 50 多种常见的健康和健身数据类型和类别,包括活动、睡眠、营养、身体测量值以及心率和血压等生命体征。
在用户许可的情况下,开发者可以使用标准化的架构和 API 行为安全地读取和写入 Health Connect。用户可以完全控制他们的隐私设置,并拥有细粒度的控制权,查看哪些应用程序在任何给定时间请求访问数据。Health Connect 中的数据存储在设备上并进行加密。用户还可以关闭访问权限或删除他们不希望在设备上保留的数据,以及在使用多个应用程序时优先选择一个数据源。
Health Connect 架构
以下是 Health Connect 的关键方面和架构组件的解释
- 客户端应用: 为了与 Health Connect 集成,客户端应用将 SDK 链接到他们的健康和健身应用中。这提供了与 Health Connect API 交互的 API 表面。
- 软件开发工具包: SDK 使客户端应用能够与 Health Connect APK 通信。
- Health Connect APK: 这是实现 Health Connect 的 APK。它包含其权限管理和数据管理组件。Health Connect APK 直接在用户的设备上提供,使 Health Connect 以设备为中心,而不是以帐户为中心。
- 权限管理: Health Connect 包含一个用户界面,通过该界面应用程序可以请求用户对显示数据的许可。它还提供现有用户权限的列表。这允许用户管理他们已授予或拒绝对各种应用程序的访问权限。
- 数据管理: Health Connect 提供一个用户界面,概述记录的数据,无论是用户步数、骑行速度、心率还是其他支持的数据类型。
您将构建什么
在本 Codelab 中,您将构建一个与 Health Connect 集成的简单健康和健身应用。您的应用程序将执行以下操作
- 获取和检查用户对数据访问的权限。
- 将数据写入 Health Connect。
- 从 Health Connect 读取聚合数据。
您将学到什么
- 如何设置环境以支持 Health Connect 集成开发。
- 如何获取权限并执行权限检查。
- 如何向 Health Connect 平台贡献健康和健身数据。
- 如何利用设备上的数据存储。
- 如何使用 Google 提供的开发者工具验证您的应用。
您需要什么
- 最新稳定版本的 Android Studio。
- 运行 Android SDK 版本 28(Pie)或更高版本的 Android 移动设备。
2. 设置
准备 Health Connect 应用
Health Connect 应用负责处理您的应用程序通过 Health Connect SDK 发送的所有请求。这些请求包括存储数据以及管理其读写访问权限。
访问 Health Connect 取决于手机上安装的 Android 版本。以下部分概述了如何处理几个最近版本的 Android。
Android 14
从 Android 14(API 级别 34)开始,Health Connect 是 Android 框架的一部分。因为此版本的 Health Connect 是一个框架模块,所以无需进行任何设置。
Android 13 及更低版本
在 Android 13(API 级别 33)和更低版本上,Health Connect 不是 Android 框架的一部分。因此,您需要从 Google Play 商店安装 Health Connect 应用。扫描二维码安装 Health Connect。
获取示例代码
示例目录包含本 Codelab 的开始和结束代码。在 Android Studio 的项目视图中,您将找到两个模块
start
:该项目的入门代码,您将对其进行修改以完成本 Codelab。finished
:本 Codelab 的完成代码,用于检查您的工作。
探索“start”代码
Codelab 示例应用具有使用 Jetpack Compose 构建的基本 UI,包含以下屏幕
WelcomeScreen
:它是应用的登录页面,显示不同的消息,具体取决于 Health Connect 的可用性,即它是否已安装、未安装或不受支持。PrivacyPolicyScreen
:它解释了应用的权限使用情况,当用户点击 Health Connect 权限对话框中的隐私政策链接时会显示。InputReadingsScreen
:它演示了如何读取和写入简单的体重记录。ExerciseSessionScreen
:用户可以在此处插入和列出锻炼课程。点击记录后,它会将用户带到ExerciseSessionDetailScreen
以显示与课程相关的更多数据。DifferentialChangesScreen
:它演示了如何获取更改令牌并从 Health Connect 获取新更改。
HealthConnectManager
存储与 Health Connect 交互的所有功能。在本 Codelab 中,我们将一步一步地指导您完成基本功能。 start
构建中的 <!-- TODO:
字符串在 Codelab 中有相应的章节,其中提供了供您插入项目的示例代码。
让我们从将 Health Connect 添加到项目开始!
添加 Health Connect 客户端 SDK
要开始使用 Health Connect SDK,您需要在 build.gradle
文件中添加一个依赖项。要查找 Health Connect 的最新版本,请查看 Jetpack 库版本。
dependencies {
// Add a dependency of Health Connect SDK
implementation "androidx.health.connect:connect-client:1.0.0-alpha11"
}
声明 Health Connect 可见性
要在应用中与 Health Connect 交互,请在 AndroidManifest.xml
中声明 Health Connect 包名
<!-- TODO: declare Health Connect visibility -->
<queries>
<package android:name="com.google.android.apps.healthdata" />
</queries>
运行开始项目
设置完毕后,运行 start
项目。此时,您应该会看到欢迎屏幕显示文本“Health Connect 已安装在此设备上”以及一个菜单抽屉。我们将在后续部分添加与 Health Connect 交互的功能。
3. 权限控制
Health Connect 建议开发者将权限请求限制在应用中使用的那些数据类型。全面请求权限会降低用户对应用的信心,并可能降低用户信任度。如果权限被拒绝超过两次,您的应用程序将被锁定。因此,权限请求将不再出现。
在本 Codelab 中,我们只需要以下权限
- 锻炼课程
- 心率
- 步数
- 消耗的总热量
- 体重
声明权限
您的应用程序读取或写入的每种数据类型都需要使用 AndroidManifest.xml
中的权限进行声明。从版本 alpha10
开始,Health Connect 使用标准的 Android 权限声明格式。
要声明所需数据类型的权限,请使用 <uses-permission>
元素,并使用权限分配其各自的名称。将它们嵌套在 <manifest>
标记内。有关权限及其对应数据类型的完整列表,请参阅 数据类型列表。
<!-- TODO: declare Health Connect permissions -->
<uses-permission android:name="android.permission.health.READ_HEART_RATE"/>
<uses-permission android:name="android.permission.health.WRITE_HEART_RATE"/>
<uses-permission android:name="android.permission.health.READ_STEPS"/>
<uses-permission android:name="android.permission.health.WRITE_STEPS"/>
<uses-permission android:name="android.permission.health.READ_EXERCISE"/>
<uses-permission android:name="android.permission.health.WRITE_EXERCISE"/>
<uses-permission android:name="android.permission.health.READ_TOTAL_CALORIES_BURNED"/>
<uses-permission android:name="android.permission.health.WRITE_TOTAL_CALORIES_BURNED"/>
<uses-permission android:name="android.permission.health.READ_WEIGHT"/>
<uses-permission android:name="android.permission.health.WRITE_WEIGHT"/>
在 AndroidManifest.xml
中声明一个意图过滤器,以处理解释您的应用程序如何使用这些权限的意图。您的应用程序需要处理此意图,并显示一个隐私政策,说明用户数据的用途和处理方式。当用户点击 Health Connect 权限对话框中的隐私政策链接时,会将此意图发送到应用程序。
<!-- TODO: Add intent filter to handle permission rationale intent -->
<intent-filter>
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
</intent-filter>
现在重新打开应用程序以查看已声明的权限。从菜单抽屉中点击设置,进入 Health Connect 设置屏幕。然后,点击应用程序权限,您应该在列表中看到Health Connect Codelab。点击Health Connect Codelab 以显示该应用程序上的读写访问权限数据类型列表。
请求权限
除了直接将用户带到 Health Connect 设置以管理权限之外,您还可以通过 Health Connect API 从应用程序中请求权限。请注意,用户可以随时更改权限,因此请确保您的应用程序检查是否已获得所需权限。在 Codelab 项目中,我们在读取或写入数据之前进行检查并发送权限请求。
HealthConnectClient
是 Health Connect API 的入口点。在 HealthConnectManager.kt
中,获取 HealthConnectClient
实例。
private val healthConnectClient by lazy { HealthConnectClient.getOrCreate(context) }
要在您的应用程序中启动请求权限对话框,首先构建一组所需数据类型的权限。您必须请求您仅使用的那些数据类型的权限。
例如,在记录体重屏幕中,您只需要授予体重数据的读写权限。我们在 InputReadingsViewModel.kt
中创建了一个权限集,如以下代码所示。
val permissions = setOf(
HealthPermission.getReadPermission(WeightRecord::class),
HealthPermission.getWritePermission(WeightRecord::class),
)
然后,在启动权限请求之前检查权限是否已授予。在 HealthConnectManager.kt
中,使用 getGrantedPermissions
检查所需数据类型的权限是否已授予。要启动权限请求,您必须使用 PermissionController.createRequestPermissionResultContract()
创建一个 ActivityResultContract
,该合约应在未授予所需权限时启动。
suspend fun hasAllPermissions(permissions: Set<String>): Boolean {
return healthConnectClient.permissionController.getGrantedPermissions().containsAll(permissions)
}
fun requestPermissionsActivityContract(): ActivityResultContract<Set<String>, Set<String>> {
return PermissionController.createRequestPermissionResultContract()
}
在 Codelab 示例应用中,如果您没有授予所需数据类型的权限,您可能会在屏幕上看到 **请求权限** 按钮。点击 **请求权限** 打开 Health Connect 权限对话框。允许所需的权限,然后返回 Codelab 应用。
4. 写入数据
让我们开始将记录写入 Health Connect。要写入体重记录,请使用体重输入值创建一个 WeightRecord
对象。请注意,Health Connect SDK 支持各种 单位 类。例如,使用 Mass.kilograms(weightInput)
以公斤为单位设置用户的体重。
写入 Health Connect 的所有数据都应指定时区偏移信息。在写入数据时指定时区偏移信息可在从 Health Connect 读取数据时提供时区信息。
创建体重记录后,使用 healthConnectClient.insertRecords
将数据写入 Health Connect。
/**
* TODO: Writes [WeightRecord] to Health Connect.
*/
suspend fun writeWeightInput(weightInput: Double) {
val time = ZonedDateTime.now().withNano(0)
val weightRecord = WeightRecord(
weight = Mass.kilograms(weightInput),
time = time.toInstant(),
zoneOffset = time.offset
)
val records = listOf(weightRecord)
try {
healthConnectClient.insertRecords(records)
Toast.makeText(context, "Successfully insert records", Toast.LENGTH_SHORT).show()
} catch (e: Exception) {
Toast.makeText(context, e.message.toString(), Toast.LENGTH_SHORT).show()
}
}
现在让我们运行应用。点击 **记录体重** 并输入新的以公斤为单位的体重记录。要验证体重记录是否已成功写入 Health Connect,请在设置中打开 Health Connect 应用,然后转到 **数据和访问 > 身材测量 > 体重 > 查看所有条目**。您应该会看到从 Health Connect Codelab 写入的新体重记录。
写入运动课程
课程是指用户进行活动的时间间隔。Health Connect 中的运动课程可以包括从跑步到羽毛球的任何活动。课程允许用户衡量基于时间的性能。这些数据记录了一段时间内测量的瞬时样本数组,例如活动期间的连续心率或位置样本。
以下示例演示了如何写入运动课程。使用 healthConnectClient.insertRecords
插入与课程关联的多个数据记录。此示例中的插入请求包括具有 ExerciseType
的 ExerciseSessionRecord
、具有步数计数的 StepsRecord
、具有 Energy
的 TotalCaloriesBurnedRecord
以及一系列 HeartRateRecord
样本。
/**
* TODO: Writes an [ExerciseSessionRecord] to Health Connect.
*/
suspend fun writeExerciseSession(start: ZonedDateTime, end: ZonedDateTime) {
healthConnectClient.insertRecords(
listOf(
ExerciseSessionRecord(
startTime = start.toInstant(),
startZoneOffset = start.offset,
endTime = end.toInstant(),
endZoneOffset = end.offset,
exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_RUNNING,
title = "My Run #${Random.nextInt(0, 60)}"
),
StepsRecord(
startTime = start.toInstant(),
startZoneOffset = start.offset,
endTime = end.toInstant(),
endZoneOffset = end.offset,
count = (1000 + 1000 * Random.nextInt(3)).toLong()
),
TotalCaloriesBurnedRecord(
startTime = start.toInstant(),
startZoneOffset = start.offset,
endTime = end.toInstant(),
endZoneOffset = end.offset,
energy = Energy.calories((140 + Random.nextInt(20)) * 0.01)
)
) + buildHeartRateSeries(start, end)
)
}
/**
* TODO: Build [HeartRateRecord].
*/
private fun buildHeartRateSeries(
sessionStartTime: ZonedDateTime,
sessionEndTime: ZonedDateTime,
): HeartRateRecord {
val samples = mutableListOf<HeartRateRecord.Sample>()
var time = sessionStartTime
while (time.isBefore(sessionEndTime)) {
samples.add(
HeartRateRecord.Sample(
time = time.toInstant(),
beatsPerMinute = (80 + Random.nextInt(80)).toLong()
)
)
time = time.plusSeconds(30)
}
return HeartRateRecord(
startTime = sessionStartTime.toInstant(),
startZoneOffset = sessionStartTime.offset,
endTime = sessionEndTime.toInstant(),
endZoneOffset = sessionEndTime.offset,
samples = samples
)
}
5. Health Connect 工具箱
Health Connect 工具箱简介
Health Connect 工具箱是一款辅助开发工具,可帮助您测试应用与 Health Connect 的集成。它可以直接读取和写入 Health Connect 数据,让您可以测试应用的 CRUD 操作。
在本 Codelab 中,我们将使用 Health Connect 工具箱来测试您刚刚实现的读写功能。
设置 Health Connect 工具箱
解压缩 ZIP 文件以获取 APK 文件。然后,要将 Toolbox APK 安装到连接的设备上,请使用 adb
。导航到 APK 所在的文件夹并运行以下命令
$ adb install HealthConnectToolbox-{Version Number}.apk
第一次打开 Health Connect 工具箱应用时,您将被带到 **应用 > 特殊应用访问 > 显示** 在其他应用上方的权限设置。此权限允许 Health Connect 工具箱在其他应用之上显示叠加层,这样您就可以在不离开正在开发的应用的情况下测试数据读写。
要管理用于测试的读写权限,您可以从工具箱应用的主屏幕打开 Health Connect 应用,或直接转到权限流程。
读取和写入 Health 记录
Health Connect 工具箱支持读取和写入所有 Health Connect 数据类型。在 Codelab 的上一节中,您已成功将体重和运动课程记录写入 Health Connect。让我们检查一下您是否可以从 Health Connect 工具箱读取数据。
在读取和写入 Health Connect 之前,必须获得用户的权限。Health Connect 工具箱也是如此。首先,接受来自 Health Connect 工具箱的权限请求。接下来,点击 **搜索图标** 从叠加菜单打开对话框,选择数据类型(如体重),然后点击 **读取 Health 记录**。您应该会看到您刚刚写入 Health Connect 的 Codelab 示例应用的记录。
要将记录插入 Health Connect,请点击 **编辑图标** 从叠加菜单打开对话框。然后,选择数据类型。让我们从工具箱插入体重记录。在下一节中,我们将向您展示如何通过 Health Connect API 读取记录并在应用中显示数据。
6. 读取数据
现在您已经使用 Codelab 示例应用和工具箱应用写入体重和运动课程记录,让我们使用 Health Connect API 读取这些记录。首先,创建一个 ReadRecordsRequest
并指定记录类型以及要从中读取的时间范围。 ReadRecordsRequest
还可以设置 dataOriginFilter
以指定要从中读取的记录的源应用。
/**
* TODO: Reads in existing [WeightRecord]s.
*/
suspend fun readWeightInputs(start: Instant, end: Instant): List<WeightRecord> {
val request = ReadRecordsRequest(
recordType = WeightRecord::class,
timeRangeFilter = TimeRangeFilter.between(start, end)
)
val response = healthConnectClient.readRecords(request)
return response.records
}
/**
* TODO: Obtains a list of [ExerciseSessionRecord]s in a specified time frame.
*/
suspend fun readExerciseSessions(start: Instant, end: Instant): List<ExerciseSessionRecord> {
val request = ReadRecordsRequest(
recordType = ExerciseSessionRecord::class,
timeRangeFilter = TimeRangeFilter.between(start, end)
)
val response = healthConnectClient.readRecords(request)
return response.records
}
现在让我们运行应用并检查您是否可以看到体重记录和运动课程的列表。
7. 读取差异数据
Health Connect 差量变更 API 有助于跟踪从特定时间点开始的一组数据类型的变更。例如,您想知道用户是否在您的应用之外更新或删除了任何现有记录,以便您可以相应地更新您的数据库。
使用 Health Connect 读取数据仅限于在前景运行的应用。此限制是为了进一步加强用户隐私。它通知并向用户保证 Health Connect 不会后台读取他们的数据,并且数据仅在前景读取和访问。当应用处于前景时,差量变更 API 使开发人员能够通过部署变更令牌来检索对 Health Connect 所做的变更。
在 HealthConnectManager.kt
中,有两个函数 getChangesToken()
和 getChanges()
。我们将向这些函数添加差量变更 API 以获取数据变更。
初始变更令牌设置
仅当您的应用使用变更令牌请求数据变更时,才会从 Health Connect 检索数据变更。变更令牌代表将从其获取差量数据的提交历史记录中的点。
要获取变更令牌,请发送 ChangesTokenRequest
,其中包含您要跟踪数据变更的数据类型集。保留令牌并在您想要从 Health Connect 检索任何更新时使用它。
/**
* TODO: Obtains a Changes token for the specified record types.
*/
suspend fun getChangesToken(): String {
return healthConnectClient.getChangesToken(
ChangesTokenRequest(
setOf(
ExerciseSessionRecord::class
)
)
)
}
使用变更令牌更新数据
当您想要从应用上次与 Health Connect 同步的时间获取变更时,请使用之前获取的变更令牌,并使用该令牌发送 getChanges
调用。 ChangesResponse
返回从 Health Connect 观察到的变更列表,例如 UpsertionChange
和 DeletionChange
。
/**
* TODO: Retrieve changes from a Changes token.
*/
suspend fun getChanges(token: String): Flow<ChangesMessage> = flow {
var nextChangesToken = token
do {
val response = healthConnectClient.getChanges(nextChangesToken)
if (response.changesTokenExpired) {
throw IOException("Changes token has expired")
}
emit(ChangesMessage.ChangeList(response.changes))
nextChangesToken = response.nextChangesToken
} while (response.hasMore)
emit(ChangesMessage.NoMoreChanges(nextChangesToken))
}
现在让我们运行应用并转到 **变更** 屏幕。首先,启用 **跟踪变更** 以获取变更令牌。然后,从工具箱或 Codelab 应用插入体重或运动课程。返回 **变更** 屏幕并选择 **获取新变更**。您现在应该会看到插入变更。
8. 聚合数据
Health Connect 还通过聚合 API 提供聚合数据。以下示例演示了如何从 Health Connect 获取累积数据和统计数据。
使用 healthConnectClient.aggregate
发送 AggregateRequest
。在聚合请求中,指定一组聚合指标以及您要获取的时间范围。例如, ExerciseSessionRecord.EXERCISE_DURATION_TOTAL
和 StepsRecord.COUNT_TOTAL
提供累积数据,而 WeightRecord.WEIGHT_AVG
、HeartRateRecord.BPM_MAX
和 HeartRateRecord.BPM_MIN
提供统计数据。
/**
* TODO: Returns the weekly average of [WeightRecord]s.
*/
suspend fun computeWeeklyAverage(start: Instant, end: Instant): Mass? {
val request = AggregateRequest(
metrics = setOf(WeightRecord.WEIGHT_AVG),
timeRangeFilter = TimeRangeFilter.between(start, end)
)
val response = healthConnectClient.aggregate(request)
return response[WeightRecord.WEIGHT_AVG]
}
本示例演示如何获取特定锻炼会话的关联聚合数据。首先,使用 healthConnectClient.readRecord
和 uid
读取记录。然后,使用锻炼会话的 startTime
和 endTime
作为时间范围,以及 dataOrigin
作为过滤器来读取关联的聚合数据。
/**
* TODO: Reads aggregated data and raw data for selected data types, for a given [ExerciseSessionRecord].
*/
suspend fun readAssociatedSessionData(
uid: String,
): ExerciseSessionData {
val exerciseSession = healthConnectClient.readRecord(ExerciseSessionRecord::class, uid)
// Use the start time and end time from the session, for reading raw and aggregate data.
val timeRangeFilter = TimeRangeFilter.between(
startTime = exerciseSession.record.startTime,
endTime = exerciseSession.record.endTime
)
val aggregateDataTypes = setOf(
ExerciseSessionRecord.EXERCISE_DURATION_TOTAL,
StepsRecord.COUNT_TOTAL,
TotalCaloriesBurnedRecord.ENERGY_TOTAL,
HeartRateRecord.BPM_AVG,
HeartRateRecord.BPM_MAX,
HeartRateRecord.BPM_MIN,
)
// Limit the data read to just the application that wrote the session. This may or may not
// be desirable depending on the use case: In some cases, it may be useful to combine with
// data written by other apps.
val dataOriginFilter = setOf(exerciseSession.record.metadata.dataOrigin)
val aggregateRequest = AggregateRequest(
metrics = aggregateDataTypes,
timeRangeFilter = timeRangeFilter,
dataOriginFilter = dataOriginFilter
)
val aggregateData = healthConnectClient.aggregate(aggregateRequest)
val heartRateData = readData<HeartRateRecord>(timeRangeFilter, dataOriginFilter)
return ExerciseSessionData(
uid = uid,
totalActiveTime = aggregateData[ExerciseSessionRecord.EXERCISE_DURATION_TOTAL],
totalSteps = aggregateData[StepsRecord.COUNT_TOTAL],
totalEnergyBurned = aggregateData[TotalCaloriesBurnedRecord.ENERGY_TOTAL],
minHeartRate = aggregateData[HeartRateRecord.BPM_MIN],
maxHeartRate = aggregateData[HeartRateRecord.BPM_MAX],
avgHeartRate = aggregateData[HeartRateRecord.BPM_AVG],
heartRateSeries = heartRateData,
)
}
现在运行应用程序,并查看您是否可以在“记录体重”屏幕上看到平均体重。您还可以通过打开“锻炼会话”屏幕并选择其中一个锻炼会话记录来查看锻炼会话的详细数据。
9. 恭喜
恭喜您已成功构建第一个 Health Connect 集成的健康和健身应用程序。
该应用程序可以声明权限并请求应用程序中使用的数据类型的用户权限。它还可以读取和写入 Health Connect 数据存储中的数据。您还学习了如何使用 Health Connect 工具箱来支持您的应用程序开发,方法是在 Health Connect 数据存储中创建模拟数据。
现在,您已经了解了将您的健康和健身应用程序纳入 Health Connect 生态系统的关键步骤。