如要在應用程式中建構睡眠追蹤體驗,可以使用健康資料同步執行下列操作:
- 寫入睡眠時段
- 寫入睡眠階段資料
- 寫入睡眠資料,例如心率、血氧濃度和呼吸速率
- 讀取其他應用程式的睡眠資料
本指南說明如何建構這些睡眠功能,涵蓋資料類型、背景執行、權限、建議的工作流程和最佳做法。
總覽:建構完整的睡眠追蹤器
如要使用「健康資料同步」建構完整的睡眠追蹤體驗,請按照下列核心步驟操作:
- 根據健康資料存取權正確實作權限。
- 使用
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 資訊清單中聲明該組權限。
// Create a set of permissions for required data types
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 權限畫面。
// 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)
}
}
由於使用者可以隨時授予或撤銷權限,應用程式每次使用權限前都必須先檢查,並處理權限遭撤銷的情況。
導入睡眠時段
本節說明記錄睡眠資料的建議工作流程。
如要將 HeartRateRecord 或 OxygenSaturationRecord 等資料類型與睡眠時段對齊,請使用介於時段 startTime 和 endTime 之間的時間戳記記錄這些資料。「健康資料同步」不會使用工作階段 ID,將睡眠工作階段與詳細資料連結。而是透過重疊的時間間隔隱含關聯。讀取睡眠資料時,您可以透過工作階段的時間範圍查詢相關聯的資料類型,如「讀取睡眠資料」一文所示。
寫入活動
雖然系統可以在睡眠期間記錄心率等詳細資料,但只有在睡眠結束後 (例如使用者醒來時),才能將 SleepSessionRecord 寫入「健康資料同步」。記錄必須包含工作階段 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 做為記錄類型,然後依時間範圍篩選。如要讀取特定睡眠階段的相關資料,請對所選資料類型發出第二個要求,例如依 startTime 和 endTime 篩選睡眠階段。HeartRateRecordstartTimeendTime
以下範例說明如何讀取特定時間範圍內的睡眠時段,以及相關聯的心率資料:
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延遲寫入作業。建議間隔 15 分鐘,在即時資料和電池效率之間取得平衡。 - 批次處理:請勿個別寫入每個感應器事件。將要求分塊。每個寫入要求最多可處理 1000 筆記錄。
- 確保工作階段 ID 穩定且不重複:為工作階段使用一致的 ID。如果編輯或更新睡眠記錄,使用相同 ID 可避免系統將其視為新的獨立睡眠記錄。
- 針對資料類型使用批次處理:如要減少輸入/輸出經常用量並延長電池續航力,請將資料點分組為單一
insertRecords呼叫,而非個別寫入每個點。 避免寫入重複資料:使用用戶端 ID 建立記錄時,請設定
metadata.clientRecordId。「健康資料同步」會使用這項資訊來識別不重複的記錄。如果您嘗試寫入已存在的clientRecordId記錄,系統會忽略重複的記錄或更新現有記錄,而不是建立新記錄。設定metadata.clientRecordId是最有效的方法,可避免在重試同步或重新安裝應用程式時發生重複問題。val record = RespiratoryRateRecord( rate = 16.0, time = time, zoneOffset = ZoneOffset.UTC, metadata = Metadata( // Use a unique ID from your own database clientRecordId = "respiratory_rate_20231030_1" ) )檢查現有資料:同步前,請查詢時間範圍,確認應用程式中是否已有記錄。
確保時間戳記不會重疊:確認新工作階段不會在前一個工作階段結束前開始。如果運動記錄重疊,健身資訊主頁和摘要計算可能會發生衝突。
提供清楚的權限理由:使用
Permission.createIntent流程說明應用程式需要存取健康資料的原因,例如「分析睡眠模式」。測試長時間工作階段:監控數小時工作階段的電池耗電量,確認批次間隔和感應器用量不會耗盡裝置電力。
將時間戳記與感應器速率對齊:將記錄時間戳記與感應器的實際頻率相符,以維持高保真度的資料。
測試
如要驗證資料正確性並確保使用者享有優質體驗,請依循下列測試策略,並參閱官方的測試熱門用途說明文件。
驗證工具
- 健康資料同步 Toolbox:使用這款隨附應用程式手動檢查記錄、刪除測試資料,以及模擬資料庫變更。這是驗證記錄是否正確儲存的最佳方式。
- 使用
FakeHealthConnectClient進行單元測試:使用測試程式庫驗證應用程式如何處理極端情況,例如權限撤銷或 API 例外狀況,不必使用實體裝置。
品質檢查清單
一般架構
睡眠追蹤功能通常包含以下項目:
| 元件 | 管理 |
|---|---|
| 工作階段控制器 | 工作階段狀態 計時器 批次處理邏輯 資料類型控制器 資料收集 |
| 存放區層 (包裝健康資料同步作業): | 插入睡眠階段 插入資料類型 插入睡眠階段 讀取睡眠階段摘要 |
| UI 層 (顯示): | 睡眠時間長度 即時資料類型 睡眠階段視覺化 |
疑難排解
| 問題 | 可能原因 | 解析度 |
|---|---|---|
| 缺少資料類型 (例如心率) | 缺少寫入權限或時間篩選器有誤。 | 確認您已要求特定資料類型權限,且使用者已授予該權限。確認 ReadRecordsRequest 使用的 TimeRangeFilter 與工作階段相符。請參閱「權限」。 |
| 工作階段無法寫入 | 時間戳記重疊。 | 如果記錄與來自同一應用程式的現有資料重疊,健康資料同步可能會拒絕。請確認新活動的 startTime晚於前一活動的 endTime。 |
| 睡眠期間未記錄感應器資料 | 前景服務已終止或無效。 | 如要在螢幕關閉時收集整夜的感應器資料,可以使用搭配 foregroundServiceType="health" 的前景服務。 |
| 出現重複記錄 | 缺少 clientRecordId |
在每筆記錄的 Metadata 中指派專屬的 clientRecordId。這樣一來,如果同步重試期間寫入相同資料兩次,健康資料同步就能執行重複資料刪除作業。請參閱「最佳做法」。 |
常見的偵錯步驟
- 檢查權限狀態:嘗試讀取或寫入作業前,請務必先呼叫
getPermissionStatus()。使用者隨時可以在系統設定中撤銷權限。 - 驗證執行模式:如果應用程式未在背景收集資料,請確認您已在
AndroidManifest.xml中聲明正確的權限,且使用者未將應用程式設為「電池用量限制」模式。