Supervisa datos en segundo plano

Las actualizaciones de datos pasivos son adecuadas para las apps que necesitan supervisar los datos de los Servicios de salud en segundo plano. Se crearon para casos de uso que abarcan horas, días o incluso más tiempo. Si necesitas almacenar o procesar datos de salud cuando tu app no está en ejecución y el usuario no participa explícitamente en un ejercicio, debes usar el cliente pasivo de los Servicios de salud.

Para ver ejemplos del uso de datos pasivos, consulta los ejemplos de datos pasivos y objetivos pasivos en GitHub.

Agrega dependencias

Para agregar una dependencia en los Servicios de salud, debes agregar el repositorio de Maven de Google a tu proyecto. Para obtener información relacionada, consulta el repositorio de Maven de Google.

En el archivo build.gradle a nivel del módulo, agrega la siguiente dependencia:

Groovy

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

Kotlin

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

Cómo verificar las capacidades

Antes de registrarte para recibir actualizaciones de datos, verifica que el dispositivo pueda proporcionar el tipo de datos que necesita tu app. La verificación de capacidades te permite habilitar o inhabilitar ciertas funciones, así como modificar la IU de tu app para compensar las funciones que no están disponibles.

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
}

Cómo registrarse para usar datos pasivos

Puedes recibir datos pasivos a través de un servicio, una devolución de llamada o ambos. Un servicio permite que tu app reciba datos en segundo plano cuando no hay una parte visible de ella en primer plano. Cuando recibes datos en segundo plano, estos se entregan en lotes. La devolución de llamada recibe datos a una velocidad un poco más rápida, pero solo mientras se ejecuta la app y se notifica correctamente dicha devolución de llamada.

Sin importar el método que uses, primero crea un PassiveListenerConfig que determine los tipos de datos que quieres recibir, como se muestra en el siguiente ejemplo:

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

Para recibir datos con una devolución de llamada, define y registra la devolución de llamada como se muestra en el siguiente ejemplo:

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

El uso de un servicio es similar, pero en lugar de crear una clase derivada de PassiveListenerCallback, deriva de PassiveListenerService, como se muestra en el siguiente ejemplo:

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

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

Luego, declara el servicio en tu archivo AndroidManifest.xml. Esto requiere un permiso de Servicios de salud, que garantiza que solo los Servicios de salud puedan vincularse al servicio:

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

Tiempo de interpretación

Los datos que recibes de los servicios de salud se agrupan en lotes, por lo que puedes recibir datos de diferentes tipos o varios datos del mismo tipo en el mismo lote. Usa las marcas de tiempo incluidas en estos objetos en lugar de la hora en que la app las recibió para determinar el orden correcto de los eventos.

A fin de obtener marcas de tiempo para cada DataPoint, primero calcula la marca de tiempo de inicio, como se muestra en el siguiente ejemplo:

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

Este valor se puede pasar a getStartInstant() o getEndInstant().

Cómo restablecer registros después del inicio

Los registros de datos pasivos no persisten entre reinicios. Para recibir datos después de reiniciar un dispositivo, vuelve a crear tus registros con un BroadcastReceiver que escuche la transmisión del sistema ACTION_BOOT_COMPLETED.

En el receptor, no intentes restablecer los registros directamente. En su lugar, debes delegar esta funcionalidad a un trabajador WorkManager. Cuando se inicia el dispositivo, los Servicios de salud pueden demorar 10 segundos o más en confirmar una solicitud de registro de datos pasivos, lo que puede exceder el tiempo de ejecución permitido de un BroadcastReceiver. Por el contrario, los trabajadores WorkManager tienen un límite de ejecución de 10 minutos.

En el siguiente fragmento, se muestra cómo se vería 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()
   }
}

Para que el sistema ejecute este código cuando se inicie el dispositivo, realiza dos cambios en el archivo file AndroidManifest.xml.

Primero, agrega el siguiente permiso como elemento secundario de <manifest>:

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

En segundo lugar, agrega el siguiente filtro de intents del receptor como elemento secundario de <application>:

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

Estado de la actividad

El cliente pasivo también puede proporcionar información general sobre el estado del usuario, por ejemplo, si el usuario está durmiendo. Para recibir estas actualizaciones, sigue estos pasos:

  1. Solicita el permiso ACTIVITY_RECOGNITION.
  2. Llama a setShouldUserActivityInfoBeRequested(true) en el compilador de PassiveListenerConfig.

Anula el método onUserActivityInfoReceived() en tu devolución de llamada o servicio, y usa el UserActivityInfo que se muestra, como se indica en el siguiente ejemplo:

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

Objetivos pasivos

Puedes configurar un cliente pasivo para que notifique a la app cuando se alcanzan los objetivos pasivos, como cuando el usuario completa 10,000 pasos en un día.

Para ello, crea un objetivo, como se muestra en el siguiente ejemplo:

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

Agrega este objetivo a tu PassiveListenerConfig, como se muestra en el siguiente ejemplo:

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

Anula el método onGoalCompleted() en tu devolución de llamada o servicio, y usa el PassiveGoal que se muestra, como se indica en el siguiente ejemplo:

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