バックグラウンドでデータをモニタリングする

パッシブなデータの更新は、バックグラウンドでヘルスサービス データをモニタリングする必要があるアプリに適しています。また、数時間、数日、またはそれ以上の期間のユースケースが想定されています。アプリが実行されていないときに健康に関するデータを保存または処理する必要があり、ユーザーが明示的にエクササイズを行わない場合は、ヘルスサービスのパッシブ クライアントを使用してください。

パッシブなデータの使用例については、以下をご覧ください。 パッシブ データ および パッシブな目標 GitHub でサンプルを確認できます。

依存関係を追加する

ヘルスサービスへの依存関係を追加するには、Google の Maven リポジトリをプロジェクトに追加する必要があります。詳しくは、Google の Maven リポジトリをご覧ください。

次に、モジュール レベルの build.gradle ファイルで、次の依存関係を追加します。

Groovy

dependencies {
    implementation "androidx.health:health-services-client:1.1.0-alpha03"
}

Kotlin

dependencies {
    implementation("androidx.health:health-services-client:1.1.0-alpha03")
}

機能を確認する

データ更新に登録する前に、デバイスがアプリに必要なタイプのデータを提供できることを確認してください。機能を確認することで、特定の機能を有効または無効にできます。または、アプリの UI を変更して、使用できない機能を補うこともできます。

val healthClient = HealthServices.getClient(this /*context*/)
val passiveMonitoringClient = healthClient.passiveMonitoringClient
lifecycleScope.launchWhenCreated {
    val capabilities = passiveMonitoringClient.capabilities.await()
    // Supported types for passive data collection
    supportsHeartRate =
        DataType.HEART_RATE_BPM in capabilities.supportedDataTypesPassiveMonitoring
    // Supported types for PassiveGoals
    supportsStepsGoal =
        DataType.STEPS_DAILY in capabilities.supportedDataTypesPassiveGoals
}

パッシブデータに登録する

サービスまたはコールバック(またはその両方)によりパッシブデータを受信できます。サービスを使用すると、アプリがフォアグラウンドにまったく表示されていなくても、バックグラウンドでデータを受信できます。バックグラウンドで受信したデータは、バッチで配信されます。コールバックはデータの受信がやや速くなりますが、これはアプリが実行中でコールバックが正常に通知を受けている間に限られます。

どの方法を使用する場合でも、まず PassiveListenerConfig を作成して、受信するデータのタイプを決定する必要があります。次の例をご覧ください。

val passiveListenerConfig = PassiveListenerConfig.builder()
    .setDataTypes(setOf(DataType.HEART_RATE_BPM))
    .build()

コールバックを使用してデータを受信するには、次の例に示すように、コールバックを定義して登録します。

val passiveListenerCallback: PassiveListenerCallback = object : PassiveListenerCallback {
    override fun onNewDataPointsReceived(dataPoints: DataPointContainer) {
        // TODO: Do something with dataPoints
    }
}

passiveMonitoringClient.setPassiveListenerCallback(
    passiveListenerConfig,
    passiveListenerCallback
)

// To remove the listener
passiveMonitoringClient.clearPassiveListenerCallbackAsync()

サービスを使用する場合とほぼ同様ですが、PassiveListenerCallback から派生したクラスを作成するのではなく、次の例のように PassiveListenerService から派生させます。

class PassiveDataService : PassiveListenerService() {
    override fun onNewDataPointsReceived(dataPoints: DataPointContainer) {
        // TODO: Do something with dataPoints
    }
}

passiveMonitoringClient.setPassiveListenerServiceAsync(
    PassiveDataService::class.java,
    passiveListenerConfig
)

次に、AndroidManifest.xml ファイルでサービスを宣言します。ヘルスサービスの権限を必須にし、ヘルスサービスのみをサービスにバインドできるようにします。

<service android:name=".PassiveDataService"
    android:permission="com.google.android.wearable.healthservices.permission.PASSIVE_DATA_BINDING"
    android:exported="true" />

時刻を解釈する

ヘルスサービスから受信したデータはバッチ処理されるため、異なるタイプのデータポイント、または同じタイプの複数のデータポイントを同じバッチで受け取ることができます。これらのオブジェクトをアプリが受け取った時刻ではなく、オブジェクト内のタイムスタンプを使用して、イベントの正しい順序を判別してください。

次の例に示すように、まず起動タイムスタンプを計算して、各 DataPoint のタイムスタンプを取得します。

val bootInstant =
    Instant.ofEpochMilli(System.currentTimeMillis() - SystemClock.elapsedRealtime())

この値は getStartInstant() または getEndInstant() に渡すことができます。

起動後に登録を復元する

パッシブなデータ登録は再起動後には保持されません。デバイスの再起動後にデータを受信するには、ACTION_BOOT_COMPLETED システム ブロードキャストをリッスンする BroadcastReceiver を使用して登録を再作成します。

受信した側で、登録を直接復元しようとしないでください。代わりに、この機能を WorkManager ワーカーに委任してください。デバイスが起動している場合、ヘルスサービスがパッシブデータ登録リクエストを承認するまで 10 秒以上かかることがあります。このため、BroadcastReceiver の許容実行時間を超える可能性があります。これに対して、WorkManager ワーカーには 10 分間の実行制限があります。

次のスニペットは BroadcastReceiver がどのようになるかを示しています。

class StartupReceiver : BroadcastReceiver() {

   override fun onReceive(context: Context, intent: Intent) {
       if (intent.action != Intent.ACTION_BOOT_COMPLETED) return


       // TODO: Check permissions first
       WorkManager.getInstance(context).enqueue(
           OneTimeWorkRequestBuilder<RegisterForPassiveDataWorker>().build()
       )
   }
}

class RegisterForPassiveDataWorker(
   private val appContext: Context,
   workerParams: WorkerParameters
) : Worker(appContext, workerParams) {

   override fun doWork(): Result {
       runBlocking {
           HealthServices.getClient(appContext)
                .passiveMonitoringClient
                .setPassiveListenerCallback(...)
       }
       return Result.success()
   }
}

デバイスの起動時にこのコードを実行するようにシステムを設定するには、AndroidManifest.xml ファイルを 2 か所変更します。

まず、次の権限を <manifest> の子として追加します。

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

次に、<application> の子として次のレシーバ インテント フィルタを追加します。

<receiver
    android:name=".StartupReceiver"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

アクティビティの状態

パッシブ クライアントは、ユーザーの状態に関する概要情報(ユーザーが眠っているかどうかなど)も提供できます。これらの更新情報を受け取る手順は次のとおりです。

  1. ACTIVITY_RECOGNITION 権限をリクエストします。
  2. PassiveListenerConfig ビルダーで setShouldUserActivityInfoBeRequested(true) を呼び出します。

コールバックまたはサービスで onUserActivityInfoReceived() メソッドをオーバーライドし、返された UserActivityInfo を使用します。次の例をご覧ください。

override fun onUserActivityInfoReceived(info: UserActivityInfo) {
    val stateChangeTime: Instant = info.stateChangeTime // may be in the past!
    val userActivityState: UserActivityState = info.userActivityState
    if (userActivityState == UserActivityState.USER_ACTIVITY_ASLEEP) {
        // ...
    }
}

パッシブ目標

ユーザーが 1 日 10,000 歩を達成するなど、パッシブ目標を達成したときにアプリに通知するようにパッシブ クライアントを設定できます。

そのためには、次の例のように目標を作成します。

val dailyStepsGoal by lazy {
    val condition = DataTypeCondition(
        dataType = DataType.STEPS_DAILY,
        threshold = 10_000, // Trigger every 10000 steps
        comparisonType = ComparisonType.GREATER_THAN_OR_EQUAL
    )
    PassiveGoal(condition)
}

次の例に示すように、この目標を PassiveListenerConfig に追加します。

val passiveListenerConfig = PassiveListenerConfig.builder()
    .setDailyGoals(setOf(dailyStepsGoal))
    .build()

コールバックまたはサービスで onGoalCompleted() メソッドをオーバーライドし、返された PassiveGoal を使用します。次の例をご覧ください。

override fun onGoalCompleted(goal: PassiveGoal) {
    when (goal.dataTypeCondition.dataType) {
        DataType.STEPS_DAILY -> {
            // ...
        }
    }
}