本指南適用於健康資料同步 1.1.0 以上版本。
如果您想在應用程式中建構健身體驗,可以使用 Health Connect 執行下列操作:
- 寫入運動時段
- 寫入訓練路線
- 寫入心率、速度和距離等健身指標
- 讀取其他應用程式的健身資料
本指南說明如何建構這些健身功能,涵蓋資料類型、背景執行、權限、建議的工作流程和最佳做法。
總覽:建立全方位的健身追蹤工具
您可以按照下列核心步驟,使用健康資料同步打造全方位的健身追蹤體驗:
- 根據 Health 權限正確導入權限。
- 使用
ExerciseSessionRecord錄製工作階段。 - 在工作階段期間持續寫入健身資料。
- 妥善管理背景執行作業,確保資料持續擷取。
- 讀取運動階段資料,提供運動後摘要和分析。
這個工作流程可與其他健康資料同步應用程式互通,並驗證使用者控管的資料存取權。
事前準備
實作健身功能前:
- 使用適當的依附元件整合健康資料同步。
- 建立
HealthConnectClient執行個體。 - 確認應用程式會根據健康資料權限實作執行階段權限流程。
- 如果工作流程使用 GPS,請設定位置存取權和前景服務。
核心概念
「健康資料同步」會使用幾個核心元件來呈現健身資料。ExerciseSessionRecord 是運動的核心記錄,包含開始或結束時間和運動類型等詳細資料。在工作階段期間,系統可以記錄各種資料類型,例如 HeartRateRecord 或 SpeedRecord。如果是戶外活動,ExerciseRoute 會儲存 GPS 資料,並連結至對應的課程。
運動時段
ExerciseSessionRecord 是健身資料的核心記錄,代表單一健身課程。每筆記錄會儲存:
startTimeendTimeexerciseType- 選填的工作階段中繼資料 (標題、附註)
ExerciseSessionRecord 也可以包含運動路線、圈數和區隔,做為資料的一部分。此外,系統也可以在工作階段期間記錄其他資料類型 (例如 HeartRateRecord 或 SpeedRecord),並與工作階段建立關聯。
相關資料類型
與健身課程相關聯的資料會以個別記錄類型表示。常見的類型包括:
HeartRateRecord:代表一系列心率測量結果。SpeedRecord:代表一系列速度測量值。DistanceRecord:代表兩次讀數之間經過的距離。TotalCaloriesBurnedRecord:代表兩次讀數之間燃燒的總卡路里。- :代表兩次讀數之間增加的高度。
ElevationGainedRecord StepsCadenceRecord:代表兩次讀數之間的步頻。PowerRecord:代表兩次讀數之間的功率輸出,常見於騎自行車等活動。
如需完整的資料類型清單,請參閱「健康資料同步資料類型」。
運動路線
您可以使用 ExerciseRoute 將路線與戶外運動建立關聯。路線由一連串 ExerciseRoute.Location 物件組成,每個物件都包含:
- 經緯度
- 選用高度
- 選用方位
- 準確度資訊
- 時間戳記
連結工作階段路徑
ExerciseRoute 包含運動工作階段的依序位置資料。在「健康資料同步」中,這類資料不會視為獨立記錄。而是在插入或更新 ExerciseSessionRecord 時提供 ExerciseRoute 資料。
開發作業注意事項
運動追蹤應用程式通常需要長時間執行,且經常在螢幕關閉時於背景執行。建構運動功能時,請務必考量如何管理背景執行作業,並要求運動資料所需的權限。
背景執行
健身應用程式通常會在螢幕關閉時執行。在這種狀態下,您應使用:
- 用於位置和感應器取樣的前景服務
WorkManager:用於延遲寫入或同步處理- 一般記錄寫入的批次處理策略
在所有寫入作業中保持工作階段 ID 一致,以維持連續性。
權限
應用程式必須先要求相關的健康資料同步權限,才能讀取或寫入健身資料。運動的常見權限包括運動課程、運動路線,以及心率或速度等指標。此時你應該可以執行下列操作:
- 運動課程:
ExerciseSessionRecord的讀取和寫入權限。 - 運動路線:
ExerciseRoute的讀取和寫入權限。 - 心率:
HeartRateRecord的讀取和寫入權限。 - 速度:
SpeedRecord的讀取和寫入權限。 - 距離:
DistanceRecord的讀取和寫入權限。 - 卡路里:
TotalCaloriesBurnedRecord的讀取和寫入權限。 - 爬升高度:
ElevationGainedRecord的讀取和寫入權限。 - 步頻:
StepsCadenceRecord的讀取和寫入權限。 - Power:
PowerRecord的讀取和寫入權限。 - 步數:
StepsRecord的讀取和寫入權限。
以下範例說明如何針對包含路線、心率、距離、卡路里、速度和步數資料的健身課程,要求多項權限:
建立用戶端執行個體後,應用程式必須要求使用者授予權限。使用者必須能隨時授予或拒絕權限。 如要這麼做,請為所需資料類型建立一組權限。請務必先在 Android 資訊清單中聲明該組權限。
val permissions = setOf( HealthPermission.getReadPermission(ExerciseSessionRecord::class), HealthPermission.getWritePermission(ExerciseSessionRecord::class), HealthPermission.getReadPermission(HeartRateRecord::class), HealthPermission.getWritePermission(HeartRateRecord::class), HealthPermission.getReadPermission(SpeedRecord::class), HealthPermission.getWritePermission(SpeedRecord::class), HealthPermission.getReadPermission(DistanceRecord::class), HealthPermission.getWritePermission(DistanceRecord::class), HealthPermission.getReadPermission(TotalCaloriesBurnedRecord::class), HealthPermission.getWritePermission(TotalCaloriesBurnedRecord::class), HealthPermission.getReadPermission(StepsRecord::class), HealthPermission.getWritePermission(StepsRecord::class) )
getGrantedPermissions 查看應用程式是否已取得所需權限。如果沒有,請使用 createRequestPermissionResultContract 要求這些權限。系統接著會顯示 Health Connect 權限畫面。
val permissions = setOf( HealthPermission.getReadPermission(StepsRecord::class), HealthPermission.getWritePermission(StepsRecord::class), HealthPermission.getReadPermission(HeartRateRecord::class), HealthPermission.getWritePermission(HeartRateRecord::class) ) val requestPermissionsLauncher = rememberLauncherForActivityResult( contract = PermissionController.createRequestPermissionResultContract() ) { grantedPermissions -> if (grantedPermissions.containsAll(permissions)) { coroutineScope.launch { snackbarHostState.showSnackbar("Permissions granted!") } } else { coroutineScope.launch { snackbarHostState.showSnackbar("Permissions denied.") } } }
如要要求權限,請呼叫 checkPermissionsAndRun 函式:
if (!granted.containsAll(permissions)) { // Check if required permissions are not granted, and return return emptySet() } // Permissions already granted; proceed with inserting or reading data
如果只需要要求單一資料類型的權限 (例如心率),請只在權限集中加入該資料類型:
心率資料的存取權受下列權限保護:
android.permission.health.READ_HEART_RATEandroid.permission.health.WRITE_HEART_RATE
如要為應用程式新增心率功能,請先要求 HeartRateRecord 資料類型的權限。
您需要宣告以下權限,才能寫入心率:
<application>
<uses-permission
android:name="android.permission.health.WRITE_HEART_RATE" />
...
</application>
如要讀取心率,您必須要求下列權限:
<application>
<uses-permission
android:name="android.permission.health.READ_HEART_RATE" />
...
</application>
導入健身課程
本節說明記錄健身資料的建議工作流程。
開始上課
如要建立新的訓練活動,請按照下列步驟操作:
- 產生專屬工作階段 ID:確認這個 ID 穩定。如果應用程式程序終止並重新啟動,您必須能夠繼續使用相同的 ID,以免工作階段片段化。
- 設定
metadata.clientRecordId,避免在重試同步處理時產生重複項目。 - 撰寫
ExerciseSessionRecord:加入開始時間。 - 開始收集資料類型和 GPS 資料:請務必在工作階段記錄成功初始化後,再開始收集這些資料。
範例:
val sessionClientId = UUID.randomUUID().toString() val zoneOffset = ZoneOffset.systemDefault().rules.getOffset(startTime) val session = ExerciseSessionRecord( startTime = startTime, startZoneOffset = zoneOffset, endTime = startTime.plusSeconds(3600), endZoneOffset = zoneOffset, exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_RUNNING, metadata = Metadata.activelyRecorded(clientRecordId = sessionClientId, device = Device(type = Device.TYPE_PHONE)), ) healthConnectClient.insertRecords(listOf(session))
記錄運動路線
如要進一步瞭解讀取指南,請參閱「讀取原始資料」。
記錄運動路線時,請批次處理資料。也就是說,您不必在每個 GPS 點出現時就儲存,而是收集一組點,然後在單一呼叫中一次儲存所有點。
這點非常重要,因為應用程式每次讀取或寫入健康資料同步時,都會消耗少量電池和處理能力。
以下程式碼說明如何批次記錄:
// 1. Create a list to hold your route locations
val routeLocations = mutableListOf<ExerciseRoute.Location>()
// 2. Add points to your list as the exercise happens
routeLocations.add(
ExerciseRoute.Location(
time = Instant.now(),
latitude = 37.7749,
longitude = -122.4194
)
)
// ... keep adding points over a period of time ...
// 3. Save the whole list at once (Batching)
val session = ExerciseSessionRecord(
startTime = startTime,
endTime = endTime,
exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_RUNNING,
// We pass the whole list here
exerciseRoute = ExerciseRoute(routeLocations)
)
healthConnectClient.insertRecords(listOf(session))
結束工作階段
停止資料收集後:
- 更新記錄:應用程式會使用
endTime更新ExerciseSessionRecord。 - 完成資料:視需要計算摘要值 (例如總距離或平均配速),並寫入為額外記錄。
val finishedSession = session.copy(endTime = Instant.now())
healthConnectClient.updateRecords(listOf(finishedSession))
讀取健身資料
應用程式可以讀取運動課程和相關資料,彙整活動摘要、提供健康深入分析,或將資料同步至外部伺服器。舉例來說,您可以讀取 ExerciseSessionRecord,然後查詢在同一時間間隔內發生的 HeartRateRecord 或 DistanceRecord。
如需將健身資料與後端伺服器同步,或讓應用程式的資料儲存庫與健康資料同步保持最新狀態,請使用 ChangeLogs。這樣一來,您就能擷取特定時間點後插入、更新或刪除的記錄清單,比手動追蹤變更或重複讀取所有資料更有效率。詳情請參閱「使用『健康資料同步』同步資料」。
讀取工作階段
如要讀取運動時段,請使用 ReadRecordsRequest,並將類型設為 ExerciseSessionRecord。通常會依特定時間範圍篩選。
suspend fun readExerciseSessions(
healthConnectClient: HealthConnectClient,
startTime: Instant,
endTime: Instant
) {
val response = healthConnectClient.readRecords(
ReadRecordsRequest(
recordType = ExerciseSessionRecord::class,
timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
)
)
for (exerciseRecord in response.records) {
// Process each session
val exerciseType = exerciseRecord.exerciseType
val notes = exerciseRecord.notes
}
}
讀取路線
雖然 ExerciseRoute 資料是做為運動課程的一部分寫入,但必須單獨讀取。使用 getExerciseRoute() 方法和工作階段 ID 讀取路線資料:
suspend fun readExerciseRoute(
healthConnectClient: HealthConnectClient,
exerciseSessionRecord: ExerciseSessionRecord
) {
// Check if the session has a route
val route = healthConnectClient.getExerciseRoute(
exerciseSessionRecordId = exerciseSessionRecord.metadata.id
)
when (route) {
is ExerciseRouteResponse.Success -> {
val locations = route.exerciseRoute.locations
for (location in locations) {
// Use latitude, longitude, and altitude
}
}
is ExerciseRouteResponse.NoData -> {
// Handle case where no route exists
}
is ExerciseRouteResponse.ConsentRequired -> {
// Handle case where permissions are missing
}
}
}
讀取資料類型
如要讀取工作階段期間發生的特定精細資料 (例如心率),請使用工作階段的 startTime 和 endTime 篩選該資料類型的要求。
suspend fun readHeartRateData(
healthConnectClient: HealthConnectClient,
exerciseSession: ExerciseSessionRecord
) {
val response = healthConnectClient.readRecords(
ReadRecordsRequest(
recordType = HeartRateRecord::class,
timeRangeFilter = TimeRangeFilter.between(
exerciseSession.startTime,
exerciseSession.endTime
)
)
)
for (heartRateRecord in response.records) {
for (sample in heartRateRecord.samples) {
val bpm = sample.beatsPerMinute
}
}
}
最佳做法
請遵循下列指南,提升資料可靠性和使用者體驗:
- 在活動追蹤期間頻繁寫入:在活動追蹤期間,請在資料可用時或每隔最多 15 分鐘寫入資料。
- 使用 WorkManager 進行背景同步:使用
WorkManager延遲寫入。建議將間隔設為 15 分鐘,在即時資料和電池效率之間取得平衡。 - 批次寫入要求:請勿個別寫入每個感應器事件,將要求分塊。健康資料同步每次寫入要求最多可處理 1000 筆記錄。
- 工作階段 ID 應保持穩定且不重複:工作階段應使用一致的 ID。如果編輯或更新工作階段,使用相同 ID 可避免系統將其視為新的獨立工作階段。
- 對資料類型和路線點使用批次處理:為減少輸入/輸出負擔並延長電池續航力,請將資料點分組到單一
insertRecords呼叫中,而不是個別寫入每個點。 - 避免寫入重複資料:使用用戶端 ID:建立記錄時,請設定
metadata.clientRecordId。健康資料同步會使用這個 ID 識別不重複的記錄。如果嘗試寫入具有現有clientRecordId的記錄,健康資料同步會忽略重複記錄或更新現有記錄,而不是建立新記錄。設定metadata.clientRecordId是最有效的方法,可避免在同步重試或重新安裝應用程式時重複。val record = StepsRecord( count = 100, startTime = startTime, endTime = endTime, startZoneOffset = ZoneOffset.UTC, endZoneOffset = ZoneOffset.UTC, metadata = Metadata( // Use a unique ID from your own database clientRecordId = "daily_steps_2023_10_27_user_123" ) )
- 檢查現有資料:同步處理前,請查詢日期範圍,確認應用程式是否已有記錄。
- 驗證 GPS 準確度:先篩除準確度低的 GPS 樣本 (例如水平準確度半徑較大的點),再寫入
ExerciseRoute,確保地圖看起來乾淨專業。 - 確保時間戳記不重疊:確認新工作階段不會在前一個工作階段結束前開始。重疊的活動可能會導致健身資訊主頁和摘要計算發生衝突。
- 清楚說明要求權限的原因:請使用
Permission.createIntent流程,說明應用程式為何需要存取健康資料,例如:「監控血壓趨勢並提供洞察資訊」。 - 支援暫停和繼續:確認應用程式能正確處理暫停。使用者暫停時,請停止收集路線點和資料類型,確保平均配速和時間長度保持準確。
- 測試長時間執行的工作階段:監控持續數小時的工作階段期間電池消耗情形,確認批次處理間隔和感應器用量不會耗盡裝置電力。
- 根據感應器頻率調整時間戳記:根據感應器的實際頻率調整記錄時間戳記,確保資料準確度。
測試
為驗證資料正確性並提供優質的使用者體驗,請遵循下列測試策略,並參閱官方的「測試主要用途」說明文件。
驗證工具
- 健康資料同步 Toolbox:使用這款隨附應用程式,手動檢查記錄、刪除測試資料,以及模擬資料庫變更。這是確認記錄是否正確儲存的最佳方式。
- 使用
FakeHealthConnectClient進行單元測試:使用測試程式庫,驗證應用程式如何處理邊緣案例,例如撤銷權限或 API 例外狀況,而不需要實體裝置。
品質檢查清單
典型架構
健身活動導入作業通常包含:
| 元件 | 管理 |
|---|---|
| 工作階段控制器 | 工作階段狀態 計時器 批次處理邏輯 資料類型控制器 位置取樣 |
| 存放區層 (包裝 健康資料同步 作業): | 插入工作階段 插入資料類型 插入路線點 讀取工作階段摘要 |
| UI 層 (顯示): | 時間長度 即時資料類型 地圖預覽 分段計算 即時 GPS 追蹤 |
疑難排解
| 問題 | 可能原因 | 解析度 |
|---|---|---|
| 路徑未與工作階段建立關聯 | 工作階段 ID 或時間範圍不符。 | 確認 ExerciseRoute 的時間範圍完全落在 ExerciseSessionRecord 期間內。如果之後會參照工作階段,請確認您使用的是一致的 ID。請參閱「記錄運動路線」。 |
| 缺少資料類型 (例如心率) | 缺少寫入權限或時間篩選器不正確。 | 請確認您已要求特定資料類型的權限,且使用者已授予該權限。確認 ReadRecordsRequest 使用與工作階段相符的 TimeRangeFilter。請參閱「權限」。 |
| 無法寫入工作階段 | 時間戳記重疊。 | 如果記錄與同一應用程式的現有資料重疊,「健康資料同步」可能會拒絕記錄。請確認新工作階段的 startTime 晚於前一個工作階段的 endTime。 |
| 未記錄 GPS 資料 | 前景服務已終止或未啟用。 | 如要在螢幕關閉時收集資料,您必須使用具有 foregroundServiceType="health" 或位置屬性的前景服務。 |
| 出現重複記錄 | 缺少 clientRecordId。 |
在每筆記錄的 Metadata 中指派專屬的 clientRecordId。這樣一來,如果同步重試期間重複寫入相同資料,健康資料同步就能執行去重複作業。請參閱「最佳做法」。 |
常見的偵錯步驟
| 檢查權限狀態。 | 嘗試執行讀取或寫入作業前,請務必呼叫 getPermissionStatus()。使用者隨時可以在系統設定中撤銷權限。 |
| 驗證執行模式。 | 如果應用程式不會在背景收集資料,請確認您已在 AndroidManifest.xml 檔案中宣告正確的權限,且使用者未將應用程式設為「電池受限」模式。 |