ヘルスコネクトで睡眠エクスペリエンスを開発する

アプリに睡眠管理機能を組み込む場合は、ヘルスコネクトを使用して次のことができます。

  • 睡眠セッションを書き込む
  • 睡眠ステージデータを書き込む
  • 心拍数、血中酸素飽和度、呼吸数などの睡眠データを書き込む
  • 他のアプリから睡眠データを読み取る

このガイドでは、これらの睡眠機能を構築する方法について説明します。データ型、バックグラウンド実行、権限、推奨されるワークフロー、ベスト プラクティスについて解説します。

概要: 包括的な睡眠トラッカーの構築

ヘルスコネクトを使用して包括的な睡眠管理機能を構築するには、次のコアステップを行います。

  • Health Permissions に基づいて権限を正しく実装する。
  • SleepSessionRecord を使用してセッションを記録する。
  • セッション中に睡眠ステージ、心拍数、血中酸素飽和度などのデータ型を一貫して書き込む。
  • バックグラウンド実行を適切に管理して、夜間の継続的なデータ収集を確認する。
  • 睡眠後の概要と分析のためにセッションデータを読み取る。

このワークフローにより、他のヘルスコネクト アプリとの相互運用が可能になり、ユーザーが制御するデータアクセスが検証されます。

始める前に

睡眠機能を実装する前に、次のことを行います。

主な概念

ヘルスコネクトは、いくつかのコア コンポーネントを使用して睡眠データを表します。SleepSessionRecord は睡眠の中央レコードとして機能し、開始時刻や終了時刻、睡眠ステージなどの詳細が含まれます。セッション中には、HeartRateRecordOxygenSaturationRecord などのさまざまなデータ型を記録できます。

睡眠セッション

睡眠データは SleepSessionRecord で表されます。各レコードには次の情報が保存されます。

  • startTime
  • endTime
  • stages: 深い睡眠、浅い睡眠、レム睡眠、覚醒状態を含む SleepSessionRecord.Stage のリスト。
  • オプションのセッション メタデータ(タイトル、メモ)

アプリは、セッションに関連付けられた複数のデータ型を書き込むことができます。

データ型

睡眠セッション中に記録される一般的なデータ型は次のとおりです。

各データ型は個別のレコードとして保存されます。

開発時の考慮事項

睡眠管理アプリは、画面がオフになっているときにバックグラウンドで長時間実行されることがよくあります。睡眠機能を構築する際は、バックグラウンド実行を管理する方法と、睡眠データに必要な権限をリクエストする方法を検討することが重要です。

バックグラウンド実行

睡眠管理アプリは、画面がオフの状態で夜間に実行されることが一般的です。この状態では、次のものを使用する必要があります。

  • データ収集用のフォアグラウンド サービス
  • 遅延書き込みまたは同期用の WorkManager
  • 心拍数などの詳細なデータの定期的なレコード書き込み用のバッチ処理戦略

すべての書き込みでセッション ID を一貫して使用して、継続性を維持します。

権限

アプリは、睡眠データを読み書きする前に、関連するヘルスコネクトの権限をリクエストする必要があります。データ型の完全なリストについては、 ヘルスコネクトのデータ型をご覧ください。睡眠に関する一般的な権限には、睡眠セッションや、心拍数や血中酸素飽和度などの指標があります。

睡眠へのアクセスは、次の権限によって保護されています。

  • android.permission.health.READ_SLEEP
  • android.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 を使用して 権限をリクエストします。ヘルスコネクトの権限画面が表示されます。

// 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)
  }
}

ユーザーはいつでも権限を付与または取り消すことができるため、アプリは権限を使用するたびに権限を確認し、権限が失われた状況に対応できるように設計する必要があります。

睡眠セッションを実装する

このセクションでは、睡眠データを記録するための推奨ワークフローについて説明します。

HeartRateRecordOxygenSaturationRecord などのデータ型を睡眠セッションに合わせるには、セッションの startTimeendTime の間のタイムスタンプで記録します。ヘルスコネクトは、セッション識別子を使用して睡眠セッションと詳細なデータをリンクしません。代わりに、時間間隔が重複することで暗黙的に関連付けられます。睡眠データを読み取る際は、セッションの 時間範囲を使用して関連するデータ型をクエリできます。これは、 睡眠データの読み取りで示すとおりです。

セッションを書き込む

心拍数などの詳細なデータは睡眠セッション全体で記録できますが、SleepSessionRecord 自体は、セッションが終了した後にのみ(ユーザーが起床したときなど)ヘルスコネクトに書き込む必要があります。SleepSessionRecord では endTimestartTime の後になる必要があるため、 レコードには セッションの startTimeendTime、セッション中に記録された SleepSessionRecord.Stage オブジェクトのリストを含める必要があります。

睡眠セッションを書き込む手順は次のとおりです。

  1. 一意のクライアント レコード ID を生成します。
  2. ユーザーが起床したとき、または睡眠管理が停止したときに、すべての睡眠ステージを収集して SleepSessionRecord を構築します。
  3. 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 をクエリできます。

関連データを含むセッションを読み取る

睡眠セッションは、レコードタイプとして SleepSessionRecord を使用し、時間範囲でフィルタした ReadRecordsRequest を使用して読み取ることができます。特定のセッションの関連データを読み取るには、選択したデータ型(HeartRateRecord など)に対して 2 回目のリクエストを行い、睡眠セッションの startTimeendTime でフィルタします。

次の例は、特定の期間の関連する心拍数データを含む睡眠セッションを読み取る方法を示しています。

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 分間隔を目指します。
    • バッチ処理: 個々のセンサー イベントを個別に書き込まないでください。リクエストをチャンクします。ヘルスコネクトは、1 回の書き込みリクエストあたり最大 1,000 件のレコードを処理します。
  • セッション ID を安定して一意に保つ: セッションには一貫した識別子を使用します。セッションが編集または更新された場合、同じ ID を使用すると、新しい別の睡眠セッションとして扱われなくなります。
  • データ型にバッチ処理を使用する: 入出力のオーバーヘッドを削減し、バッテリー寿命を維持するには、各ポイントを個別に書き込むのではなく、データポイントを 1 つの 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 フローを使用して、アプリが健康データへのアクセスを必要とする理由(「睡眠パターンを分析するため」など)を説明します。

  • 長時間実行されるセッションをテストする: 数時間続くセッション中のバッテリー消費量をモニタリングして、バッチ処理間隔とセンサーの使用量がデバイスを消耗しないことを確認します。

  • タイムスタンプをセンサーレートに合わせる: レコードのタイムスタンプをセンサーの実際の頻度に合わせて、データの忠実度を維持します。

テスト

データの正確性と高品質のユーザー エクスペリエンスを確認するには、次の テスト戦略に沿って、公式のテストの主なユースケース ドキュメントを参照してください。

確認ツール

  • ヘルスコネクト ツールボックス: このコンパニオン アプリを使用して、レコードを手動で検査し、テストデータを削除して、データベースの変更をシミュレートします。レコードが正しく保存されていることを確認するのに最適な方法です。
  • FakeHealthConnectClient を使用した単体テスト: テスト ライブラリを使用して、実機を使用せずに、権限の取り消しや API 例外などのエッジケースをアプリがどのように処理するかを確認します。

品質チェックリスト

一般的なアーキテクチャ

睡眠管理の実装には通常、次のものが含まれます。

コンポーネント 管理
セッション コントローラ セッションの状態
タイマー
バッチ処理ロジック
データ型コントローラ
データの収集
リポジトリ レイヤ(ヘルスコネクト オペレーションをラップします): セッションを挿入する
データ型を挿入する
睡眠ステージを挿入する
セッションの概要を読み取る
UI レイヤ(表示): 期間
ライブデータ型
睡眠ステージの可視化

トラブルシューティング

内容 考えられる原因 解決策
データ型がない(心拍数など) 書き込み権限がないか、時間フィルタが正しくありません。 特定のデータ型の権限をリクエストし、ユーザーが付与していることを確認します。ReadRecordsRequest がセッションと一致する TimeRangeFilter を使用していることを確認します。権限をご覧ください。
セッションの書き込みに失敗する タイムスタンプが重複しています。 ヘルスコネクトは、同じアプリの既存のデータと重複するレコードを拒否する場合があります。新しいセッションの startTime が前のセッションの endTime の後であることを確認します。
睡眠中にセンサーデータが記録されない フォアグラウンド サービスが強制終了されたか、非アクティブです。 画面がオフの状態で夜間にセンサーデータを収集するには、フォアグラウンド サービスforegroundServiceType="health"使用します。
重複するレコードが表示される clientRecordId がありません。 各レコードの Metadata に一意の clientRecordId を割り当てます。これにより、同期の再試行中に同じデータが 2 回書き込まれた場合に、ヘルスコネクトで重複排除を実行できます。ベスト プラクティスをご覧ください。

一般的なデバッグ手順

権限の状態を確認します。 読み取りまたは書き込みオペレーションを試みる前に、必ず getPermissionStatus() を呼び出してください。ユーザーはシステム設定でいつでも権限を取り消すことができます。
実行モードを確認します。 アプリがバックグラウンドでデータを収集していない場合は、AndroidManifest.xml ファイルで正しい権限が宣言されていることと、ユーザーがアプリを [バッテリー制限] モードにしていないことを確認します。