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

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

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

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

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

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

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

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

Котлин

// Using Kotlin function references
task.addOnSuccessListener(::handleDataItem)
task.addOnFailureListener(::handleDataItemError)
task.addOnCompleteListener(::handleTaskComplete)
...
fun handleDataItem(dataItem: DataItem) { ... }
fun handleDataItemError(exception: Exception) { ... }
fun handleTaskComplete(task: Task<DataItem>) { ... }

Ява

// Using Java 8 Lambdas.
task.addOnSuccessListener(dataItem -> handleDataItem(dataItem));
task.addOnFailureListener(exception -> handleDataItemError(exception));
task.addOnCompleteListener(task -> handleTaskComplete(task));

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

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

Если ваш код выполняется в отдельном потоке обработчика в фоновой службе, например в WearableListenerService , вызовы можно блокировать. В этом случае вы можете вызвать Tasks.await() для объекта Task , который блокируется до завершения запроса и не возвращает объект Result . Это показано в следующем примере.

Примечание. Обязательно не вызывайте это в основном потоке.

Котлин

try {
    Tasks.await(dataItemTask).apply {
        Log.d(TAG, "Data item set: $uri")
    }
}
catch (e: ExecutionException) { ... }
catch (e: InterruptedException) { ... }

Ява

try {
    DataItem item = Tasks.await(dataItemTask);
    Log.d(TAG, "Data item set: " + item.getUri());
} catch (ExecutionException | InterruptedException e) {
  ...
}

Слушайте события уровня данных

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

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

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

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

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

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

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

Некоторые из событий, которые вы можете прослушивать с помощью WearableListenerService , следующие:

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

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

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

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

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

В следующем примере показано, как реализовать простой WearableListenerService :

Котлин

private const val TAG = "DataLayerSample"
private const val START_ACTIVITY_PATH = "/start-activity"
private const val DATA_ITEM_RECEIVED_PATH = "/data-item-received"

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)
                }
    }
}

Ява

public class DataLayerListenerService extends WearableListenerService {
    private static final String TAG = "DataLayerSample";
    private static final String START_ACTIVITY_PATH = "/start-activity";
    private static final String DATA_ITEM_RECEIVED_PATH = "/data-item-received";

    @Override
    public void onDataChanged(DataEventBuffer dataEvents) {
        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.
        for (DataEvent event : dataEvents) {
            Uri uri = event.getDataItem().getUri();

            // Get the node ID from the host value of the URI.
            String nodeId = uri.getHost();
            // Set the data of the message to be the bytes of the URI.
            byte[] payload = uri.toString().getBytes();

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

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

Используйте фильтры с WearableListenerService

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

<service android:name=".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 заменяет ранее рекомендованное действие BIND_LISTENER , так что только определенные события пробуждают или запускают ваше приложение. Это изменение повышает эффективность системы, снижает расход заряда батареи и другие накладные расходы, связанные с вашим приложением. В этом примере часы прослушивают элемент данных /start-activity , а телефон — ответ на сообщение /data-item-received .

Применяются стандартные правила сопоставления фильтров 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() {
        Wearable.getDataClient(this).addListener(this)
    }

    override fun 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)
            }
        }
    }
}

Ява

public class MainActivity extends Activity implements DataClient.OnDataChangedListener {

    @Override
    public void onResume() {
        Wearable.getDataClient(this).addListener(this);
    }

    @Override
    protected void onPause() {
        Wearable.getDataClient(this).removeListener(this);
    }

    @Override
    public void onDataChanged(DataEventBuffer dataEvents) {
        for (DataEvent event : dataEvents) {
            if (event.getType() == DataEvent.TYPE_DELETED) {
                Log.d(TAG, "DataItem deleted: " + event.getDataItem().getUri());
            } else if (event.getType() == DataEvent.TYPE_CHANGED) {
                Log.d(TAG, "DataItem changed: " + event.getDataItem().getUri());
            }
        }
    }
}

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

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

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