Как обрабатывать события уровня данных на устройствах Wear

Когда вы вызываете Data Layer API, вы можете получить статус вызова после его завершения. Вы также можете отслеживать события, связанные с изменением данных, которые ваше приложение вносит в сеть Wear OS by Google.

Пример эффективного использования Data Layer API можно найти в приложении Android DataLayer Sample.

Ожидание статуса вызовов уровня данных

Вызовы Data Layer API, например вызов с использованием метода putDataItem класса DataClient, иногда возвращают объект Task<ResultType>. Как только объект Task будет создан, операция будет поставлена в очередь в фоновом режиме. Если вы не предпримете никаких действий, операция завершится автоматически.

Однако обычно после завершения операции с результатом нужно что-то сделать, поэтому объект Task позволяет дождаться статуса результата асинхронно или синхронно.

Асинхронные вызовы

Если ваш код выполняется в основном потоке UI, не делайте блокирующие вызовы к API уровня данных и используйте сопрограмму для вызова putDataItem:

private suspend fun Context.sendDataAsync(count: Int) {
    try {
        val putDataReq: PutDataRequest = PutDataMapRequest.create("/count").run {
            dataMap.putInt("count_key", count)
            asPutDataRequest()
        }
        val dataItem = Wearable.getDataClient(this).putDataItem(putDataReq).await()
        handleDataItem(dataItem)
    } catch (e: Exception) {
        handleDataItemError(e)
    } finally {
        handleTaskComplete()
    }
}

private fun handleDataItem(dataItem: DataItem) { }
private fun handleDataItemError(exception: Exception) { }
private fun handleTaskComplete() { }

Другие возможности, в том числе объединение выполнения разных задач, описаны в Task API.

Синхронные вызовы

Если код выполняется в отдельном потоке обработчика в фоновой службе, например в WearableListenerService, используйте runBlocking, чтобы сделать блокирующий вызов putDataItem.

Примечание. Не вызывайте этот метод в основном потоке.

private fun Context.sendDataSync(count: Int) = runBlocking {
    val putDataReq = PutDataMapRequest.create("/count").run {
        dataMap.putInt("count_key", count)
        asPutDataRequest()
    }

    try {
        val result = Wearable.getDataClient(this@sendDataSync)
            .putDataItem(putDataReq)
            .await()
        // Logic for success
    } catch (e: Exception) {
        // Handle failure
    }
}

Как отслеживать события уровня данных

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

Чтобы прослушивать события уровня данных, можно использовать два варианта:

В обоих случаях вы переопределяете методы обратного вызова событий данных для тех событий, которые хотите обрабатывать.

Примечание. При выборе реализации прослушивателя учитывайте расход заряда батареи. WearableListenerService зарегистрирован в манифесте приложения и может запустить его, если оно ещё не запущено. Если вам нужно отслеживать события только тогда, когда приложение уже запущено (что часто бывает в интерактивных приложениях), не используйте WearableListenerService. Вместо этого зарегистрируйте слушателя трансляции. Например, используйте метод addListener класса DataClient. Это может снизить нагрузку на систему и уменьшить расход заряда батареи.

Используйте WearableListenerService

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

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

С помощью WearableListenerService можно прослушивать следующие события:

  • onDataChanged(): когда объект элемента данных создается, удаляется или изменяется, система запускает этот обратный вызов на всех подключенных узлах.
  • onMessageReceived() – сообщение, отправленное из узла, запускает этот обратный вызов в целевом узле.
  • onCapabilityChanged() – когда в сети становится доступна функция, которую рекламирует экземпляр вашего приложения, это событие вызывает обратный вызов. Если вам нужен ближайший узел, вы можете запросить метод isNearby() у узлов, предоставленных в обратном вызове.

Вы также можете отслеживать события из ChannelClient.ChannelCallback, например onChannelOpened().

Все предыдущие события выполняются в фоновом потоке, а не в основном.

Чтобы создать WearableListenerService, выполните следующие действия:

  1. Создайте класс, который расширяет WearableListenerService.
  2. Прослушивайте интересующие вас события, например onDataChanged().
  3. Объявите фильтр интентов в манифесте Android, чтобы уведомить систему о вашем WearableListenerService. Эта декларация позволяет системе привязывать ваш сервис по мере необходимости.

В примере ниже показано, как реализовать WearableListenerService:

class DataLayerListenerService : WearableListenerService() {

    override fun onDataChanged(dataEvents: DataEventBuffer) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "onDataChanged: $dataEvents")
        }

        // Loop through the events and send a message
        // to the node that created the data item.
        dataEvents
            .map { it.dataItem.uri }
            .forEach { uri ->
                // Get the node ID from the host value of the URI.
                val nodeId: String = uri.host!!
                // Set the data of the message to be the bytes of the URI.
                val payload: ByteArray = uri.toString().toByteArray()

                // Send the RPC.
                Wearable.getMessageClient(this)
                    .sendMessage(
                        nodeId,
                        DATA_ITEM_RECEIVED_PATH,
                        payload
                    )
            }
    }
}

В следующем разделе рассказывается, как использовать фильтр интентов с этим прослушивателем.

Как использовать фильтры с WearableListenerService

Фильтр интентов для примера WearableListenerService, приведенного в предыдущем разделе, может выглядеть следующим образом:

<service
    android:name=".snippets.datalayer.DataLayerListenerService"
    android:exported="true"
    tools:ignore="ExportedService" >
    <intent-filter>
        <action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
        <data
            android:scheme="wear"
            android:host="*"
            android:path="/start-activity" />
    </intent-filter>
</service>

Фильтр действий DATA_CHANGED сообщает системе, что ваше приложение заинтересовано в событиях уровня данных.

В этом примере часы прослушивают элемент данных /start-activity, а телефон – ответ на сообщение /data-item-received (DATA_ITEM_RECEIVED_PATH).

Применяются стандартные правила фильтрации для Android. В манифесте можно указать несколько сервисов, в каждом сервисе – несколько фильтров намерений, в каждом фильтре – несколько действий, а в каждом фильтре – несколько блоков данных. Фильтры могут соответствовать хосту с подстановочным знаком или определенному хосту. Чтобы сопоставить с подстановочным знаком хоста, используйте host="*". Чтобы сопоставить определенный хост, укажите host=<node_id>.

Вы также можете сопоставить точный путь или префикс пути. Для этого необходимо указать подстановочный знак или определенный хост. В противном случае система игнорирует указанный вами путь.

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

Дополнительную информацию о фильтрах данных и правилах сопоставления можно найти в справочной документации по API для элемента манифеста <data>.

При сопоставлении фильтров намерений следует помнить два важных правила:

  • Если для фильтра интентов не указана схема, система игнорирует все остальные атрибуты URI.
  • Если для фильтра не указан хост, система игнорирует все атрибуты пути.

Как использовать прослушиватель событий в реальном времени

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

Чтобы сделать код более понятным и безопасным, используйте наблюдатель жизненного цикла. Используя наблюдатель жизненного цикла, вы переносите логику регистрации из метода LifecycleResumeEvent класса Activity в отдельный класс, который можно использовать повторно и который реализует интерфейс DefaultLifecycleObserver.

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

1. Как создать слушателя с учетом жизненного цикла

Этот класс содержит DataClient.OnDataChangedListener и автоматически управляет собственной подпиской на основе жизненного цикла объекта Activity.

class WearDataLayerObserver(
    private val dataClient: DataClient,
    private val onDataReceived: (DataEventBuffer) -> Unit
) : DefaultLifecycleObserver, DataClient.OnDataChangedListener {

    // Implementation of the DataClient listener
    override fun onDataChanged(dataEvents: DataEventBuffer) {
        onDataReceived(dataEvents)
    }

    // Automatically register when the Activity starts
    override fun onResume(owner: LifecycleOwner) {
        dataClient.addListener(this)
    }

    // Automatically unregister when the Activity pauses
    override fun onPause(owner: LifecycleOwner) {
        dataClient.removeListener(this)
    }
}

2. Использование в истории действий

Теперь для работы с Wear API в действиях не нужно использовать LifecycleResumeEvent или onPause. Наблюдатель регистрируется один раз в LaunchedEvent (или onCreate).

class DataLayerLifecycleActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val dataClient = Wearable.getDataClient(this)

        // Create the observer and link it to the activity's lifecycle
        val wearObserver = WearDataLayerObserver(dataClient) { dataEvents ->
            handleDataEvents(dataEvents)
        }

        lifecycle.addObserver(wearObserver)
    }

    private fun handleDataEvents(dataEvents: DataEventBuffer) {
        // ... filter and process events ...
    }
}

Преимущества:

  • Очистка Activity: Вы удаляете стандартный код из методов жизненного цикла объекта activity.
  • Безопасность. DefaultLifecycleObserver помогает убедиться, что слушатель удален, даже если объект Activity уничтожен неожиданно, предотвращая утечки памяти.
  • Повторное использование. Вы можете подключить WearDataLayerObserver к любому компоненту Activity или Composable, не переписывая логику регистрации.
  • Разделение. Логика того, когда нужно прослушивать, отделена от логики того, что делать с данными.

Как использовать фильтры для слушателей

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

Часто используется следующий подход: зарегистрировать прослушиватель с определенным путем или префиксом пути с помощью collectAsStateWithLifecycle(). Благодаря этому приложение будет получать только нужные события, что повысит его эффективность и улучшит дизайн.