Obsługa zdarzeń warstwy danych na Wear

Gdy wywołujesz interfejs Data Layer API, po zakończeniu wywołania możesz otrzymać jego stan. Możesz też nasłuchiwać zdarzeń danych wynikających ze zmian danych wprowadzanych przez aplikację w dowolnym miejscu sieci Wear OS by Google.

Przykład skutecznego korzystania z interfejsu Data Layer API znajdziesz w aplikacji Android DataLayer Sample.

Oczekiwanie na stan wywołań warstwy danych

Wywołania interfejsu Data Layer API, np. wywołanie za pomocą metody putDataItem klasy DataClient, czasami zwracają obiekt Task<ResultType>. Gdy tylko zostanie utworzony obiekt Task, operacja zostanie umieszczona w kolejce w tle. Jeśli nie podejmiesz żadnych dalszych działań, operacja zostanie ostatecznie zakończona bez powiadomienia.

Zwykle jednak po zakończeniu operacji chcesz coś zrobić z wynikiem, więc obiekt Task umożliwia asynchroniczne lub synchroniczne oczekiwanie na stan wyniku.

Połączenia asynchroniczne

Jeśli kod jest uruchamiany w głównym wątku interfejsu, nie wykonuj blokujących wywołań interfejsu Data Layer API i używaj korutyny do wywoływania 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() { }

Więcej możliwości, w tym łączenie wykonywania różnych zadań, znajdziesz w interfejsie Task API.

Połączenia synchroniczne

Jeśli kod jest uruchamiany w osobnym wątku obsługi w usłudze działającej w tle, np. w WearableListenerService, użyj runBlocking, aby wykonać blokujące wywołanie putDataItem.

Uwaga: nie wywołuj tej funkcji w głównym wątku.

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

Nasłuchiwanie zdarzeń warstwy danych

Warstwa danych synchronizuje i wysyła dane między urządzeniami przenośnymi i do noszenia, więc zwykle musisz nasłuchiwać ważnych zdarzeń, takich jak tworzenie elementów danych i odbieranie wiadomości.

Aby nasłuchiwać zdarzeń warstwy danych, masz 2 możliwości:

W przypadku obu tych opcji zastępujesz metody wywołania zwrotnego zdarzenia danych dla zdarzeń, którymi chcesz się zajmować.

Uwaga: podczas wybierania implementacji odbiornika weź pod uwagę zużycie baterii przez aplikację. WearableListenerService jest zarejestrowany w pliku manifestu aplikacji i może ją uruchomić, jeśli nie jest jeszcze uruchomiona. Jeśli chcesz nasłuchiwać zdarzeń tylko wtedy, gdy aplikacja jest już uruchomiona (co często ma miejsce w przypadku aplikacji interaktywnych), nie używaj znaku WearableListenerService. Zamiast tego zarejestruj detektor na żywo. Na przykład użyj metody addListener klasy DataClient. Może to zmniejszyć obciążenie systemu i zużycie baterii.

Używanie WearableListenerService

Zwykle tworzysz instancje WearableListenerService w aplikacjach na urządzenia przenośne i na urządzenia do noszenia. Jeśli jednak nie interesują Cię zdarzenia związane z danymi w jednej z aplikacji, nie musisz wdrażać w niej tej usługi.

Możesz na przykład mieć aplikację na urządzenie przenośne, która ustawia i pobiera obiekty elementów danych, oraz aplikację na urządzenie do noszenia, która nasłuchuje tych aktualizacji, aby aktualizować interfejs. Aplikacja na urządzenie do noszenia nigdy nie aktualizuje żadnych elementów danych, więc aplikacja na urządzenie przenośne nie nasłuchuje żadnych zdarzeń danych z aplikacji na urządzenie do noszenia.

Oto niektóre zdarzenia, których możesz nasłuchiwać za pomocą interfejsu WearableListenerService:

  • onDataChanged(): gdy obiekt elementu danych zostanie utworzony, usunięty lub zmieniony, system aktywuje to wywołanie zwrotne we wszystkich połączonych węzłach.
  • onMessageReceived(): wiadomość wysłana z węzła wywołuje to wywołanie zwrotne w węźle docelowym.
  • onCapabilityChanged(): gdy w sieci stanie się dostępna funkcja, którą reklamuje instancja Twojej aplikacji, to zdarzenie wywoła to wywołanie zwrotne. Jeśli szukasz węzła w pobliżu, możesz wysłać zapytanie do metody isNearby() węzłów podanych w wywołaniu zwrotnym.

Możesz też nasłuchiwać zdarzeń z ChannelClient.ChannelCallback, takich jak onChannelOpened().

Wszystkie poprzednie zdarzenia są wykonywane w wątku w tle, a nie w wątku głównym.

Aby utworzyć WearableListenerService, wykonaj te czynności:

  1. Utwórz klasę, która rozszerza klasę WearableListenerService.
  2. Nasłuchuj zdarzeń, które Cię interesują, np. onDataChanged().
  3. Zadeklaruj filtr intencji w pliku manifestu Androida, aby powiadomić system o WearableListenerService. Ta deklaracja umożliwia systemowi powiązanie usługi w razie potrzeby.

Poniższy przykład pokazuje, jak wdrożyć 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
                    )
            }
    }
}

W sekcji poniżej wyjaśniamy, jak używać filtra intencji z tym odbiornikiem.

Korzystanie z filtrów w usłudze WearableListenerService

Filtr intencji dla przykładu WearableListenerService z poprzedniej sekcji może wyglądać tak:

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

Filtr działania DATA_CHANGED informuje system, że Twoja aplikacja jest zainteresowana zdarzeniami warstwy danych.

W tym przykładzie zegarek nasłuchuje elementu danych /start-activity, a telefon nasłuchuje odpowiedzi na wiadomość /data-item-received (DATA_ITEM_RECEIVED_PATH).

Obowiązują standardowe reguły dopasowywania filtrów Androida. W jednym pliku manifestu możesz określić wiele usług, w jednym filtrze intencji – wiele działań, a w jednym filtrze – wiele sekcji danych. Filtry mogą pasować do hosta z symbolem wieloznacznym lub do konkretnego hosta. Aby dopasować hosta z symbolem wieloznacznym, użyj host="*". Aby dopasować konkretnego hosta, podaj host=<node_id>.

Możesz też dopasować dosłowną ścieżkę lub prefiks ścieżki. Aby to zrobić, musisz określić symbol wieloznaczny lub konkretnego hosta. W przeciwnym razie system zignoruje podaną ścieżkę.

Więcej informacji o typach filtrów obsługiwanych przez Wear OS znajdziesz w dokumentacji interfejsu API WearableListenerService.

Więcej informacji o filtrach danych i regułach dopasowywania znajdziesz w dokumentacji interfejsu API elementu manifestu <data>.

Podczas dopasowywania filtrów intencji pamiętaj o 2 ważnych zasadach:

  • Jeśli w filtrze intencji nie określono schematu, system ignoruje wszystkie inne atrybuty URI.
  • Jeśli w filtrze nie określono hosta, system ignoruje wszystkie atrybuty ścieżki.

Korzystanie z usługi Live Listener

Jeśli Twoja aplikacja potrzebuje zdarzeń warstwy danych tylko wtedy, gdy użytkownik wchodzi z nią w interakcję, może nie potrzebować długotrwałej usługi do obsługi każdej zmiany danych. W takim przypadku możesz nasłuchiwać zdarzeń w aktywności.

Aby zaproponować czystsze i bezpieczniejsze podejście, użyj Lifecycle Observer. Dzięki użyciu obserwatora cyklu życia przenosisz logikę rejestracji z onResume() aktywności do osobnej klasy wielokrotnego użytku, która implementuje DefaultLifecycleObserver.

Dzięki temu Twoja aktywność będzie prosta i unikniesz typowych błędów, takich jak zapomnienie o wyrejestrowaniu detektora.

1. Tworzenie odbiornika uwzględniającego cykl życia

Ta klasa opakowuje DataClient.OnDataChangedListener i automatycznie zarządza własną subskrypcją na podstawie cyklu życia aktywności.

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. Wykorzystanie w sekcji Aktywność

Teraz Twoja aktywność nie musi zastępować onResume() ani onPause() w przypadku interfejsu Wear API. Obserwatora dodajesz tylko raz w 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 ...
    }
}

Dlaczego ta metoda jest lepsza:

  • Cleaner Activity: usuwasz powtarzalny kod z metod cyklu życia aktywności.
  • Bezpieczeństwo: DefaultLifecycleObserver pomaga sprawdzić, czy odbiornik został usunięty, nawet jeśli aktywność została nieoczekiwanie zniszczona, co zapobiega wyciekom pamięci.
  • Wielokrotne użycie: możesz podłączyć ten WearDataLayerObserver do dowolnego komponentu Activity lub Fragment bez ponownego pisania logiki rejestracji.
  • Odseparowanie: logika określająca, kiedy należy nasłuchiwać, jest oddzielona od logiki określającej, co zrobić z danymi.

Używanie filtrów w przypadku słuchaczy na żywo

Jak wspomnieliśmy wcześniej, tak jak możesz określać filtry intencji dla obiektów WearableListenerService opartych na pliku manifestu, możesz używać filtrów intencji podczas rejestrowania aktywnego odbiornika za pomocą Wearable API. Te same zasady obowiązują zarówno w przypadku słuchaczy korzystających z interfejsu API, jak i słuchaczy korzystających z pliku manifestu.

Często stosowanym wzorcem jest rejestrowanie odbiornika z określoną ścieżką lub prefiksem ścieżki za pomocą LifecycleObserver. Dzięki implementacji odbiorników w ten sposób aplikacja może bardziej selektywnie odbierać zdarzenia, co poprawia jej projekt i wydajność.