Мониторинг данных в фоновом режиме

Пассивное обновление данных подходит для приложений, которым необходимо отслеживать данные служб здравоохранения в фоновом режиме. Они предназначены для случаев использования, которые охватывают часы, дни или даже дольше. Если вам необходимо хранить или обрабатывать данные о состоянии здоровья, когда ваше приложение не работает и пользователь явно не участвует в упражнениях, используйте пассивный клиент Health Service.

Примеры использования пассивных данных см. в примерах пассивных данных и пассивных целей на GitHub.

Добавить зависимости

Чтобы добавить зависимость от Health Services, необходимо добавить в свой проект репозиторий 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")
}

Проверить возможности

Прежде чем регистрироваться для получения обновлений данных, убедитесь, что устройство может предоставить тот тип данных, который необходим вашему приложению. Проверка возможностей позволяет включать или отключать определенные функции или изменять пользовательский интерфейс вашего приложения, чтобы компенсировать недоступные функции.

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 . Требуется разрешение Health Services, которое гарантирует, что только Health Services сможет привязаться к службе:

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

Восстановление регистрации после загрузки

Пассивная регистрация данных не сохраняется после перезагрузки. Чтобы получить данные после перезапуска устройства, заново создайте свои регистрации с помощью BroadcastReceiver , который прослушивает системную трансляцию ACTION_BOOT_COMPLETED .

В приемнике не пытайтесь восстановить регистрацию напрямую. Вместо этого делегируйте эту функцию работнику 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 .

Сначала добавьте следующее разрешение в качестве дочернего элемента <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. Вызовите setShouldUserActivityInfoBeRequested(true) в построителе PassiveListenerConfig .

Переопределите метод 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) {
        // ...
    }
}

Пассивные цели

Вы можете настроить пассивный клиент так, чтобы он уведомлял приложение о достижении пассивных целей, например о выполнении пользователем 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 -> {
            // ...
        }
    }
}
{% дословно %} {% дословно %} {% дословно %} {% дословно %}