Обработка событий уровня данных на 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) = 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.
  • Если для фильтра не указан хост, система игнорирует все атрибуты пути.

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

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

Чтобы порекомендовать более чистый и безопасный подход, используйте Lifecycle Observer . Используя Lifecycle Observer, вы переносите логику регистрации из метода onResume() 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. Использование в вашей деятельности

Теперь вашему Activity не нужно переопределять onResume() или onPause() для Wear API. Вы добавляете наблюдателя один раз в 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 или Fragment без переписывания логики регистрации.
  • Разделение: логика определения момента прослушивания отделена от логики обработки данных.

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

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

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