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 wprowadzonych przez aplikację w dowolnym miejscu sieci Wear OS by Google.

Przykład skutecznej pracy z interfejsem Data Layer API znajdziesz w aplikacji przykładowej Android DataLayer.

Czekanie na stan wywołań warstwy danych

Wywołania interfejsu Data Layer API, takie jak wywołanie za pomocą metody putDataItem klasy DataClient, czasami zwracają obiekt Task<ResultType>. Gdy tylko obiekt Task zostanie utworzony, operacja jest umieszczana w kolejce w tle. Jeśli po tym nie zrobisz nic więcej, operacja w końcu zakończy się bez powiadomienia.

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

Wywołania asynchroniczne

Jeśli Twój kod działa w głównym wątku UI, nie wykonuj blokujących wywołań interfejsu Data Layer API i użyj współprogramu do wywołania 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 informacji o innych możliwościach, w tym o łączeniu wykonania różnych zadań, znajdziesz w dokumentacji interfejsu Task API.

Wywołania synchroniczne

Jeśli Twój kod działa 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 metody 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

Ponieważ warstwa danych synchronizuje i wysyła dane między urządzeniami przenośnymi i ubieralnymi, 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 obu przypadkach zastępujesz metody wywołania zwrotnego zdarzeń danych dla zdarzeń, które Cię interesują.

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

Korzystanie z WearableListenerService

Zwykle tworzysz instancje WearableListenerService zarówno w aplikacji na urządzenie ubieralne, jak i na urządzenie przenośne. Jeśli jednak nie interesują Cię zdarzenia danych w jednej z aplikacji, nie musisz implementować usługi w tej aplikacji.

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ądzenia do noszenia, która nasłuchuje tych aktualizacji, aby zaktualizować interfejs. Aplikacja na urządzenia 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ądzenia do noszenia.

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

  • onDataChanged(): gdy element danych zostanie utworzony, usunięty lub zmieniony, system wywołuje 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 isNearby() metody węzłów podanych w wywołaniu zwrotnym.

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

Wszystkie wymienione wyżej zdarzenia są wykonywane w wątku w tle, a nie w głównym wątku.

Aby utworzyć WearableListenerService:

  1. Utwórz klasę, która rozszerza 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 Twojej usługi w razie potrzeby.

Poniższy przykład pokazuje, jak zaimplementować 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śniono, jak używać filtra intencji z tym odbiornikiem.

Używanie filtrów z WearableListenerService

Filtr intencji dla przykładu WearableListenerService pokazanego w 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 pliku manifestu możesz określić wiele usług, wiele filtrów intencji na usługę, wiele działań na filtr i wiele sekcji danych na filtr. 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, określ host=<node_id>.

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

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

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

Podczas dopasowywania filtrów intencji pamiętaj o 2 ważnych regułach:

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

Korzystanie z odbiornika na żywo

Jeśli Twoja aplikacja interesuje się zdarzeniami 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 uzyskać czystsze i bezpieczniejsze rozwiązanie, użyj obserwatora cyklu życia. Dzięki użyciu obserwatora cyklu życia przenosisz logikę rejestracji z aktywności LifecycleResumeEvent do osobnej klasy wielokrotnego użytku, która implementuje DefaultLifecycleObserver.

Dzięki temu aktywność jest bardziej przejrzysta i zapobiega typowym błędom, takim jak zapomnienie o wyrejestrowaniu odbiornika.

1. Utwórz odbiornik uwzględniający 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. Użycie w aktywności

Teraz aktywność nie musi używać LifecycleResumeEvent ani onPause w przypadku interfejsu Wear API. Obserwatora rejestrujesz raz w LaunchedEvent (lub 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 to jest lepsze:

  • Czysta aktywność: usuwasz kod powtarzalny z metod cyklu życia aktywności.
  • Bezpieczeństwo: DefaultLifecycleObserver pomaga sprawdzić, czy odbiornik został usunięty, nawet jeśli aktywność zostanie nieoczekiwanie zniszczona, co zapobiega wyciekom pamięci.
  • Wielokrotne użycie: możesz podłączyć ten WearDataLayerObserver do dowolnej aktywności lub funkcji Composable bez konieczności ponownego pisania logiki rejestracji.
  • Oddzielenie: logika dotycząca tego, kiedy nasłuchiwać, jest oddzielona od logiki dotyczącej tego, co zrobić z danymi.

Używanie filtrów z odbiornikami na żywo

Jak wspomnieliśmy wcześniej, tak jak możesz określić filtry intencji dla obiektów opartych na pliku manifestu WearableListenerService, możesz używać filtrów intencji podczas rejestrowania odbiornika na żywo za pomocą interfejsu Wearable API. Te same reguły obowiązują zarówno w przypadku odbiorników na żywo opartych na interfejsie API, jak i odbiorników opartych na pliku manifestu.

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