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

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

Пример эффективной работы с API уровня данных можно найти в демонстрационном приложении Android DataLayer Sample .

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

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

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

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

Если ваш код выполняется в основном потоке пользовательского интерфейса, не используйте блокирующие вызовы к 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() { }

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

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

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

Примечание: Не вызывайте эту функцию в основном потоке.

private fun Context.sendDataSync(count: Int) {
    // Create a data item with the path and data to be sent
    val putDataReq: PutDataRequest = PutDataMapRequest.create("/count").run {
        dataMap.putInt("count_key", count)
        asPutDataRequest()
    }
    // Create a task to send the data to the data layer
    val task: Task<DataItem> = Wearable.getDataClient(this).putDataItem(putDataReq)
    try {
        Tasks.await(task).apply {
            // Add your logic here
        }
    } catch (e: ExecutionException) {
        // TODO: Handle exception
    } catch (e: InterruptedException) {
        // TODO: Handle exception
        Thread.currentThread().interrupt()
    }
}

Отслеживайте события уровня данных.

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

Для отслеживания событий уровня данных у вас есть два варианта:

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

Примечание: При выборе реализации обработчика событий учитывайте энергопотребление вашего приложения. 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.
  • Если для фильтра не указан хост, система игнорирует все атрибуты пути.

Используйте слушателя в режиме реального времени

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

Чтобы создать действие, которое будет отслеживать события данных, выполните следующие шаги:

  1. Реализуйте необходимые интерфейсы.
  2. В методах onCreate() или onResume() вызовите Wearable.getDataClient(this).addListener() , MessageClient.addListener() , CapabilityClient.addListener() или ChannelClient.registerChannelCallback() , чтобы уведомить сервисы Google Play о том, что ваша активность заинтересована в событиях уровня данных.
  3. В onStop() или onPause() отмените регистрацию всех слушателей с помощью DataClient.removeListener() , MessageClient.removeListener() , CapabilityClient.removeListener() или ChannelClient.unregisterChannelCallback() .
  4. Если активности необходимо получать только события с определенным префиксом пути, добавьте слушатель с фильтром по префиксу, чтобы получать только данные, относящиеся к текущему состоянию приложения.
  5. Реализуйте onDataChanged() , onMessageReceived() , onCapabilityChanged() или методы из ChannelClient.ChannelCallback в зависимости от реализованных вами интерфейсов. Эти методы вызываются в основном потоке, или вы можете указать собственный Looper с помощью WearableOptions .

Вот пример реализации метода DataClient.OnDataChangedListener :

class MainActivity : Activity(), DataClient.OnDataChangedListener {

    public override fun onResume() {
        super.onResume()
        Wearable.getDataClient(this).addListener(this)
    }

    override fun onPause() {
        super.onPause()
        Wearable.getDataClient(this).removeListener(this)
    }

    override fun onDataChanged(dataEvents: DataEventBuffer) {
        dataEvents.forEach { event ->
            if (event.type == DataEvent.TYPE_DELETED) {
                Log.d(TAG, "DataItem deleted: " + event.dataItem.uri)
            } else if (event.type == DataEvent.TYPE_CHANGED) {
                Log.d(TAG, "DataItem changed: " + event.dataItem.uri)
            }
        }
    }
}

Внимание: перед использованием API Wearable Data Layer убедитесь, что он доступен на устройстве; в противном случае возникнет исключение. Используйте класс GoogleApiAvailability , реализованный в Horologist .

Используйте фильтры для прослушивания в режиме реального времени.

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

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