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

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

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

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

Чтобы добавить зависимость от Health Services, необходимо добавить репозиторий Google Maven в свой проект. Подробнее см. в репозитории Google Maven .

В файле build.gradle уровня модуля добавьте следующую зависимость:

классный

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

Котлин

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

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

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

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 . При запуске устройства службам Health Services может потребоваться 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 -> {
            // ...
        }
    }
}
{% дословно %} {% endverbatim %} {% дословно %} {% endverbatim %}