Daten im Hintergrund überwachen

Passive Datenaktualisierungen eignen sich für Anwendungen, die Daten von Gesundheitsdiensten im Hintergrund überwachen müssen. Sie sind für Anwendungsfälle vorgesehen, die Stunden, Tage oder noch länger dauern. Wenn du Gesundheitsdaten speichern oder verarbeiten musst, während deine Anwendung nicht ausgeführt wird und der Nutzer nicht explizit an einem Training beteiligt ist, verwende den passiven Client von Health Service.

Beispiele für die passive Datennutzung finden Sie in den Beispielen für Passive Daten und Passive Ziele auf GitHub.

Abhängigkeiten hinzufügen

Zum Hinzufügen einer Abhängigkeit von Health Services müssen Sie Ihrem Projekt das Maven-Repository von Google hinzufügen. Weitere Informationen finden Sie unter Maven-Repository von Google.

Fügen Sie in der Datei build.gradle auf Modulebene die folgende Abhängigkeit hinzu:

Groovig

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

Kotlin

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

Funktionen prüfen

Bevor du dich für Datenaktualisierungen registrierst, solltest du prüfen, ob das Gerät den Datentyp bereitstellen kann, den deine App benötigt. Mithilfe der Überprüfung der Funktionen können Sie bestimmte Funktionen aktivieren oder deaktivieren oder die Benutzeroberfläche Ihrer App ändern, um nicht verfügbare Funktionen auszugleichen.

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
}

Für passive Daten registrieren

Du kannst passive Daten über einen Dienst, einen Callback oder beides empfangen. Mit einem Dienst kann Ihre App Daten im Hintergrund empfangen, wenn kein Teil der App im Vordergrund sichtbar ist. Wenn Sie Daten im Hintergrund empfangen, werden sie in Batches bereitgestellt. Der Callback empfängt Daten etwas schneller, aber nur, während die App ausgeführt wird und der Callback erfolgreich benachrichtigt wird.

Unabhängig von der verwendeten Methode müssen Sie zuerst einen PassiveListenerConfig erstellen, der bestimmt, welche Datentypen empfangen werden sollen, wie im folgenden Beispiel gezeigt:

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

Definieren und registrieren Sie den Callback, wie im folgenden Beispiel gezeigt, um Daten mithilfe eines Callbacks zu erhalten:

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()

Die Verwendung eines Dienstes ist ähnlich. Statt eine von PassiveListenerCallback abgeleitete Klasse zu erstellen, leiten Sie jedoch von PassiveListenerService ab, wie im folgenden Beispiel gezeigt:

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

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

Als Nächstes deklarieren Sie den Dienst in der Datei AndroidManifest.xml. Erfordern Sie eine Berechtigung für Gesundheitsdienste, mit der sichergestellt wird, dass sich nur Gesundheitsdienste an den Dienst binden können:

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

Zeit interpretieren

Die Daten, die Sie von Health Services erhalten, werden in Batches zusammengefasst. Daher können Sie im selben Batch Datenpunkte unterschiedlicher Typen oder mehrere Datenpunkte desselben Typs erhalten. Verwenden Sie die Zeitstempel in diesen Objekten und nicht die Zeit, zu der sie von Ihrer App empfangen wurden, um die richtige Reihenfolge der Ereignisse zu bestimmen.

Berechnen Sie zuerst den Bootzeitstempel, um Zeitstempel für jede DataPoint abzurufen, wie im folgenden Beispiel gezeigt:

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

Dieser Wert kann dann an getStartInstant() oder getEndInstant() übergeben werden.

Registrierungen nach dem Start wiederherstellen

Passive Datenregistrierungen gehen bei Neustarts verloren. Damit du nach dem Neustart eines Geräts Daten empfangen kannst, musst du deine Registrierungen mit einem BroadcastReceiver neu erstellen, der auf den System-Broadcast ACTION_BOOT_COMPLETED wartet.

Versuchen Sie nicht, die Registrierungen im Empfänger direkt wiederherzustellen. Delegieren Sie diese Funktion stattdessen an einen WorkManager-Worker. Wenn das Gerät hochfährt, kann es 10 Sekunden oder länger dauern, bis die Gesundheitsdienste eine Anfrage zur passiven Datenregistrierung bestätigen. Dadurch kann die zulässige Ausführungszeit von BroadcastReceiver überschritten werden. Im Gegensatz dazu haben WorkManager-Worker ein Ausführungslimit von 10 Minuten.

Das folgende Snippet zeigt, wie ein BroadcastReceiver aussehen könnte:

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

Damit das System diesen Code beim Hochfahren des Geräts ausführt, nehmen Sie zwei Änderungen an der Datei AndroidManifest.xml vor.

Fügen Sie zuerst die folgende Berechtigung als untergeordnetes Element von <manifest> hinzu:

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

Fügen Sie dann den folgenden Empfänger-Intent-Filter als untergeordnetes Element von <application> hinzu:

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

Aktivitätsstatus

Der passive Client kann auch allgemeine Informationen zum Nutzerstatus liefern, z. B. ob der Nutzer schläft. So erhalten Sie diese Updates:

  1. Fordern Sie die Berechtigung ACTIVITY_RECOGNITION an.
  2. Rufen Sie setShouldUserActivityInfoBeRequested(true) im PassiveListenerConfig-Builder auf.

Überschreiben Sie die Methode onUserActivityInfoReceived() in Ihrem Callback oder Dienst und verwenden Sie den zurückgegebenen UserActivityInfo-Wert, wie im folgenden Beispiel gezeigt:

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

Passive Ziele

Sie können einen passiven Client konfigurieren, der die App benachrichtigt, wenn passive Ziele erreicht werden, z. B. wenn ein Nutzer 10.000 Schritte an einem Tag ausführt.

Erstellen Sie dazu ein Ziel, wie im folgenden Beispiel gezeigt:

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

Fügen Sie dieses Zielvorhaben zu PassiveListenerConfig hinzu, wie im folgenden Beispiel gezeigt:

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

Überschreiben Sie die Methode onGoalCompleted() in Ihrem Callback oder Dienst und verwenden Sie den zurückgegebenen PassiveGoal-Wert, wie im folgenden Beispiel gezeigt:

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