本指南適用於健康資料同步 1.1.0 以上版本。
如要在應用程式中建構睡眠追蹤體驗,可以使用健康資料同步執行下列操作:
- 寫入睡眠時段
- 寫入睡眠階段資料
- 寫入睡眠資料,例如心率、血氧濃度和呼吸速率
- 讀取其他應用程式的睡眠資料
本指南說明如何建構這些睡眠功能,涵蓋資料類型、背景執行、權限、建議的工作流程和最佳做法。
總覽:建構全方位的睡眠追蹤工具
您可以按照下列核心步驟,使用健康資料同步打造全方位的睡眠追蹤體驗:
- 根據 Health 權限正確導入權限。
- 使用
SleepSessionRecord錄製工作階段。 - 在工作階段期間,持續寫入睡眠階段、心率和血氧濃度等資料類型。
- 妥善管理背景執行作業,確保整晚持續擷取資料。
- 讀取睡眠階段資料,提供睡眠後摘要和分析。
這個工作流程可與其他健康資料同步應用程式互通,並驗證使用者控管的資料存取權。
事前準備
導入睡眠功能前:
- 使用適當的依附元件整合健康資料同步。
- 建立
HealthConnectClient執行個體。 - 確認應用程式會根據健康權限實作執行階段權限流程。
核心概念
健康資料同步會使用幾個核心元件來表示睡眠資料。SleepSessionRecord 是睡眠資料的中央記錄,包含開始或結束時間和睡眠階段等詳細資料。在工作階段期間,系統可以記錄各種資料類型,例如 HeartRateRecord 或 OxygenSaturationRecord。
睡眠時段
睡眠資料會以 SleepSessionRecord 表示。每筆記錄會儲存:
startTimeendTimestages:SleepSessionRecord.Stage清單,包括深層睡眠、淺層睡眠、快速動眼睡眠和清醒睡眠。- 選填的工作階段中繼資料 (標題、附註)
應用程式可能會寫入與工作階段相關聯的多種資料類型。
資料類型
睡眠時段記錄的常見資料類型包括:
SleepSessionRecord:記錄睡眠時長和階段,包括深層睡眠、淺層睡眠、快速動眼睡眠和清醒睡眠。HeartRateRecord:記錄睡眠期間的心率。OxygenSaturationRecord:記錄睡眠期間的血氧濃度 (SpO2)。RespiratoryRateRecord:記錄睡眠期間的呼吸速率。
每種資料類型都會儲存為個別記錄。
開發作業注意事項
睡眠追蹤應用程式通常需要長時間執行,而且經常在螢幕關閉時於背景執行。建構睡眠功能時,請務必考量如何管理背景執行作業,並要求睡眠資料所需的權限。
背景執行
睡眠追蹤應用程式通常會在螢幕關閉時整夜執行。在這種狀態下,您應使用:
- 用於資料收集的前景服務
WorkManager用於延遲寫入或同步- 批次處理策略,用於定期寫入精細資料 (例如心率) 的記錄
在所有寫入作業中保持工作階段 ID 一致,以維持連續性。
權限
應用程式必須先要求相關的健康資料同步權限,才能讀取或寫入睡眠資料。如需完整的資料類型清單,請參閱「健康資料同步資料類型」。睡眠的常見權限包括睡眠時段,以及心率或血氧濃度等指標。
睡眠資料的存取權受下列權限保護:
android.permission.health.READ_SLEEPandroid.permission.health.WRITE_SLEEP
如要為應用程式新增睡眠功能,請先要求 SleepSession 資料類型的權限。
您需要宣告以下權限,才能寫入睡眠資料:
<application>
<uses-permission
android:name="android.permission.health.WRITE_SLEEP" />
...
</application>
如要讀取睡眠資料,您必須要求下列權限:
<application>
<uses-permission
android:name="android.permission.health.READ_SLEEP" />
...
</application>
以下範例說明如何要求睡眠時段的權限,包括心率、血氧濃度和呼吸速率資料:
建立用戶端執行個體後,應用程式必須要求使用者授予權限。使用者必須能隨時授予或拒絕權限。 如要這麼做,請為所需資料類型建立一組權限。請務必先在 Android 資訊清單中聲明該組權限。
val permissions = setOf( HealthPermission.getReadPermission(SleepSessionRecord::class), HealthPermission.getWritePermission(SleepSessionRecord::class), HealthPermission.getReadPermission(HeartRateRecord::class), HealthPermission.getWritePermission(HeartRateRecord::class), HealthPermission.getReadPermission(OxygenSaturationRecord::class), HealthPermission.getWritePermission(OxygenSaturationRecord::class), HealthPermission.getReadPermission(RespiratoryRateRecord::class), HealthPermission.getWritePermission(RespiratoryRateRecord::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.") } } }
導入睡眠時段
本節說明記錄睡眠資料的建議工作流程。
如要將 HeartRateRecord 或 OxygenSaturationRecord 等資料類型與睡眠時段對齊,請使用介於時段 startTime 和 endTime 之間的時間戳記記錄。「健康資料同步」不會使用工作階段 ID,將睡眠時段與精細資料連結。而是透過重疊的時間間隔隱含關聯。讀取睡眠資料時,您可以使用工作階段的日期範圍查詢相關聯的資料類型,如「讀取睡眠資料」所示。
寫入工作階段
雖然系統可以在整個睡眠時段記錄心率等精細資料,但只有在時段結束後 (例如使用者醒來時),才能將 SleepSessionRecord 本身寫入 Health Connect。記錄必須包含工作階段 startTime、endTime,以及工作階段期間記錄的 SleepSessionRecord.Stage 物件清單,因為 SleepSessionRecord 要求 endTime 必須在 startTime 之後。
如要寫入睡眠時段,請按照下列步驟操作:
- 產生專屬的用戶端記錄 ID。
- 當使用者醒來或睡眠追蹤停止時,請收集所有睡眠階段並建構
SleepSessionRecord。 - 使用
insertRecords插入記錄。
範例:
val clientRecordId = UUID.randomUUID().toString()
val sessionStartTime = LocalDateTime.of(2023, 10, 30, 22, 0).toInstant(ZoneOffset.UTC)
val sessionEndTime = LocalDateTime.of(2023, 10, 31, 7, 0).toInstant(ZoneOffset.UTC)
val stages = mutableListOf<SleepSessionRecord.Stage>()
// Add recorded stages, for example:
stages.add(SleepSessionRecord.Stage(
startTime = sessionStartTime.plusSeconds(3600),
endTime = sessionStartTime.plusSeconds(7200),
stage = SleepSessionRecord.STAGE_TYPE_LIGHT)
)
stages.add(SleepSessionRecord.Stage(
startTime = sessionStartTime.plusSeconds(7200),
endTime = sessionStartTime.plusSeconds(10800),
stage = SleepSessionRecord.STAGE_TYPE_DEEP)
)
// ... other stages
val session = SleepSessionRecord(
startTime = sessionStartTime,
startZoneOffset = ZoneOffset.UTC,
endTime = sessionEndTime,
endZoneOffset = ZoneOffset.UTC,
stages = stages,
metadata = Metadata(clientRecordId = clientRecordId)
)
healthConnectClient.insertRecords(listOf(session))
讀取睡眠資料
應用程式可以讀取睡眠時段和相關資料,彙整活動、提供健康深入分析,或與外部伺服器同步資料。例如,您可以讀取 SleepSessionRecord,然後查詢在同一時間間隔內發生的 HeartRateRecord。
讀取含有相關聯資料的工作階段
您可以使用 ReadRecordsRequest 讀取睡眠時段,並將 SleepSessionRecord 設為記錄類型,然後依日期範圍篩選。如要讀取特定睡眠時段的相關資料,請針對所選資料類型 (例如 HeartRateRecord) 提出第二項要求,並依睡眠時段的 startTime 和 endTime 進行篩選。
以下範例說明如何讀取指定日期範圍內的睡眠時段,以及相關聯的心率資料:
suspend fun readSleepSessionsWithAssociatedData(
healthConnectClient: HealthConnectClient,
startTime: Instant,
endTime: Instant
) {
val response = healthConnectClient.readRecords(
ReadRecordsRequest(
recordType = SleepSessionRecord::class,
timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
)
)
for (sleepRecord in response.records) {
// Process each session
val stages = sleepRecord.stages
val notes = sleepRecord.notes
// To read specific granular data (like heart rate) that occurred during
// this session, use the session's startTime and endTime to filter
// the request for that data type.
val hrResponse = healthConnectClient.readRecords(
ReadRecordsRequest(
recordType = HeartRateRecord::class,
timeRangeFilter = TimeRangeFilter.between(
sleepRecord.startTime,
sleepRecord.endTime
)
)
)
for (heartRateRecord in hrResponse.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" ) )
- 檢查現有資料:同步處理前,請查詢日期範圍,確認應用程式是否已有記錄。
- 確保時間戳記不重疊:確認新工作階段不會在前一個工作階段結束前開始。重疊的活動可能會導致健身資訊主頁和摘要計算發生衝突。
- 清楚說明要求權限的原因:請使用
Permission.createIntent流程,說明應用程式為何需要存取健康資料,例如:「監控血壓趨勢並提供洞察資訊」。 - 測試長時間執行的工作階段:監控持續數小時的工作階段期間電池消耗情形,確認批次處理間隔和感應器用量不會耗盡裝置電力。
- 根據感應器頻率調整時間戳記:根據感應器的實際頻率調整記錄時間戳記,確保資料準確度。
測試
為驗證資料正確性並提供優質的使用者體驗,請遵循下列測試策略,並參閱官方的「測試主要用途」說明文件。
驗證工具
- 健康資料同步 Toolbox:使用這款隨附應用程式,手動檢查記錄、刪除測試資料,以及模擬資料庫變更。這是確認記錄是否正確儲存的最佳方式。
- 使用
FakeHealthConnectClient進行單元測試:使用測試程式庫,驗證應用程式如何處理邊緣案例,例如撤銷權限或 API 例外狀況,而不需要實體裝置。
品質檢查清單
典型架構
睡眠追蹤導入作業通常包含:
| 元件 | 管理 |
|---|---|
| 工作階段控制器 | 工作階段狀態 計時器 批次處理邏輯 資料類型控制器 資料收集 |
| 存放區層 (包裝 健康資料同步 作業): | 插入工作階段 插入資料類型 插入睡眠階段 讀取工作階段摘要 |
| UI 層 (顯示): | 持續時間 即時資料類型 睡眠階段圖表 |
疑難排解
| 問題 | 可能原因 | 解析度 |
|---|---|---|
| 缺少資料類型 (例如心率) | 缺少寫入權限或時間篩選器不正確。 | 請確認您已要求特定資料類型的權限,且使用者已授予該權限。確認 ReadRecordsRequest 使用與工作階段相符的 TimeRangeFilter。請參閱「權限」。 |
| 無法寫入工作階段 | 時間戳記重疊。 | 如果記錄與同一應用程式的現有資料重疊,「健康資料同步」可能會拒絕記錄。請確認新工作階段的 startTime 晚於前一個工作階段的 endTime。 |
| 睡眠期間未記錄任何感應器資料 | 前景服務已終止或未啟用。 | 如要在螢幕關閉時整夜收集感應器資料,可以使用前景服務搭配 foregroundServiceType="health"。 |
| 出現重複記錄 | 缺少 clientRecordId。 |
在每筆記錄的 Metadata 中指派專屬的 clientRecordId。這樣一來,如果同步重試期間重複寫入相同資料,健康資料同步就能執行去重複作業。請參閱「最佳做法」。 |
常見的偵錯步驟
| 檢查權限狀態。 | 嘗試執行讀取或寫入作業前,請務必呼叫 getPermissionStatus()。使用者隨時可以在系統設定中撤銷權限。 |
| 驗證執行模式。 | 如果應用程式不會在背景收集資料,請確認您已在 AndroidManifest.xml 檔案中宣告正確的權限,且使用者未將應用程式設為「電池受限」模式。 |