Monitora i dati in background

Gli aggiornamenti passivi dei dati sono adatti alle app che devono monitorare i dati di Servizi per la salute in background. Sono pensati per casi d'uso che durano ore, giorni o anche più a lungo. Se devi archiviare o elaborare dati di salute e fitness quando l'app non è in esecuzione e l'utente non è impegnato esplicitamente in un allenamento, utilizza il client passivo di Health Service.

Per esempi di utilizzo dei dati passivi, consulta gli esempi di dati passivi e obiettivi passivi su GitHub.

Aggiungi dipendenze

Per aggiungere una dipendenza da Health Services, devi aggiungere il repository Maven di Google al tuo progetto. Per saperne di più, consulta il repository Maven di Google.

Nel file build.gradle a livello di modulo, aggiungi la seguente dipendenza:

Groovy

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

Kotlin

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

Controllare le funzionalità

Prima di registrarti per gli aggiornamenti dei dati, verifica che il dispositivo possa fornire il tipo di dati di cui la tua app ha bisogno. Il controllo delle funzionalità consente di attivare o disattivare determinate funzionalità o modificare la UI dell'app per compensare le funzionalità non disponibili.

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
}

Registrarsi per i dati passivi

Puoi ricevere dati passivi tramite un servizio, un callback o entrambi. Un servizio consente alla tua app di ricevere dati in background quando nessuna parte della tua app è visibile in primo piano. Quando ricevi dati in background, questi vengono forniti in batch. Il callback riceve i dati a una velocità leggermente superiore, ma solo mentre l'app è in esecuzione e il callback viene notificato correttamente.

Qualunque metodo utilizzi, crea innanzitutto un PassiveListenerConfig che determini i tipi di dati da ricevere, come mostrato nell'esempio seguente:

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

Per ricevere dati utilizzando un callback, definisci e registra il callback, come mostrato nell'esempio seguente:

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

L'utilizzo di un servizio è simile, ma anziché creare una classe derivata da PassiveListenerCallback, deriva da PassiveListenerService, come mostrato nell'esempio seguente:

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

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

Successivamente, dichiara il servizio nel file AndroidManifest.xml. Richiedi un'autorizzazione Servizi per la salute, che verifica che solo Servizi per la salute possa eseguire il binding al servizio:

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

Interpretare l'ora

I dati che ricevi da Servizi per la salute sono raggruppati in batch, quindi potresti ricevere punti dati di tipi diversi o più punti dati dello stesso tipo nello stesso batch. Utilizza i timestamp inclusi in questi oggetti anziché l'ora in cui sono stati ricevuti dalla tua app per determinare l'ordine corretto degli eventi.

Ottieni i timestamp per ogni DataPoint calcolando prima il timestamp di avvio, come mostrato nell'esempio seguente:

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

Questo valore può essere poi passato a getStartInstant() o getEndInstant().

Ripristinare le registrazioni dopo l'avvio

Le registrazioni di dati passivi non vengono mantenute dopo i riavvii. Per ricevere i dati dopo il riavvio di un dispositivo, ricrea le registrazioni utilizzando un BroadcastReceiver che ascolta la trasmissione di sistema ACTION_BOOT_COMPLETED.

Nel destinatario, non tentare di ripristinare direttamente le registrazioni. Delega invece questa funzionalità a un worker WorkManager. All'avvio del dispositivo, i servizi per la salute potrebbero impiegare 10 secondi o più per confermare una richiesta di registrazione passiva dei dati e questo potrebbe superare il tempo di esecuzione consentito di un BroadcastReceiver. Al contrario, i worker WorkManager hanno un limite di esecuzione di 10 minuti.

Il seguente snippet mostra l'aspetto di un 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()
   }
}

Per fare in modo che il sistema esegua questo codice all'avvio del dispositivo, apporta due modifiche al file AndroidManifest.xml.

Innanzitutto, aggiungi la seguente autorizzazione come tag secondario di <manifest>:

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

In secondo luogo, aggiungi il seguente filtro di intent del ricevitore come elemento secondario di <application>:

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

Stato dell'attività

Il client passivo può anche fornire informazioni di alto livello sullo stato dell'utente, ad esempio se sta dormendo. Per ricevere questi aggiornamenti, segui questi passaggi:

  1. Richiedi l'autorizzazione ACTIVITY_RECOGNITION.
  2. Chiama setShouldUserActivityInfoBeRequested(true) nel generatore PassiveListenerConfig.

Esegui l'override del metodo onUserActivityInfoReceived() nel callback o nel servizio e utilizza il valore UserActivityInfo restituito, come mostrato nell'esempio seguente:

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

Obiettivi passivi

Puoi configurare un client passivo per notificare all'app quando vengono raggiunti gli obiettivi passivi, ad esempio quando l'utente completa 10.000 passi in un giorno.

Per farlo, crea un obiettivo, come mostrato nell'esempio seguente:

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

Aggiungi questo obiettivo al tuo PassiveListenerConfig, come mostrato nell'esempio seguente:

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

Esegui l'override del metodo onGoalCompleted() nel callback o nel servizio e utilizza il valore PassiveGoal restituito, come mostrato nell'esempio seguente:

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