Tworzenie zaawansowanego widżetu

Na tej stronie opisujemy zalecane metody tworzenia bardziej zaawansowanych widżetów, które zwiększają wygodę użytkowników.

Optymalizacje w zakresie aktualizowania zawartości widżetów

Aktualizowanie zawartości widżetu może być kosztowne pod względem obliczeń. Aby zmniejszyć zużycie baterii, zoptymalizuj typ, częstotliwość i czas aktualizacji.

Rodzaje aktualizacji widżetów

Widżet można aktualizować na 3 sposoby: pełna aktualizacja, aktualizacja częściowa oraz, w przypadku widżetu kolekcji, odświeżenie danych. Każdy z nich niesie ze sobą inne koszty obliczeniowe i konsekwencje.

Poniżej opisujemy poszczególne typy aktualizacji i przedstawiamy fragmenty kodu dla każdego z nich.

  • Pełna aktualizacja: wywołaj AppWidgetManager.updateAppWidget(int, android.widget.RemoteViews), aby w pełni zaktualizować widżet. Spowoduje to zastąpienie wcześniej podanej wartości RemoteViews nowym elementem RemoteViews. To najdroższa aktualizacja pod względem obliczeń.

    Kotlin

    val appWidgetManager = AppWidgetManager.getInstance(context)
    val remoteViews = RemoteViews(context.getPackageName(), R.layout.widgetlayout).also {
    setTextViewText(R.id.textview_widget_layout1, "Updated text1")
    setTextViewText(R.id.textview_widget_layout2, "Updated text2")
    }
    appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
    

    Java

    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
    RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widgetlayout);
    remoteViews.setTextViewText(R.id.textview_widget_layout1, "Updated text1");
    remoteViews.setTextViewText(R.id.textview_widget_layout2, "Updated text2");
    appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
    
  • Częściowa aktualizacja: wywołaj AppWidgetManager.partiallyUpdateAppWidget, by zaktualizować części widżetu. Spowoduje to scalenie nowego RemoteViews z wcześniej podanym RemoteViews. Ta metoda jest ignorowana, jeśli widżet nie otrzyma co najmniej jednej pełnej aktualizacji do updateAppWidget(int[], RemoteViews).

    Kotlin

    val appWidgetManager = AppWidgetManager.getInstance(context)
    val remoteViews = RemoteViews(context.getPackageName(), R.layout.widgetlayout).also {
    setTextViewText(R.id.textview_widget_layout, "Updated text")
    }
    appWidgetManager.partiallyUpdateAppWidget(appWidgetId, remoteViews)
    

    Java

    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
    RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widgetlayout);
    remoteViews.setTextViewText(R.id.textview_widget_layout, "Updated text");
    appWidgetManager.partiallyUpdateAppWidget(appWidgetId, remoteViews);
    
  • Odświeżanie danych kolekcji: wywołaj AppWidgetManager.notifyAppWidgetViewDataChanged, aby unieważnić dane widoku kolekcji w widżecie. Spowoduje to aktywowanie RemoteViewsFactory.onDataSetChanged. Do tego czasu w widżecie wyświetlane są stare dane. Dzięki tej metodzie możesz bezpiecznie wykonywać drogie zadania synchronicznie.

    Kotlin

    val appWidgetManager = AppWidgetManager.getInstance(context)
    appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_listview)
    
    

    Java

    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
    appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_listview);
    

Możesz wywoływać te metody z dowolnego miejsca w aplikacji, jeśli ma ona taki sam identyfikator UID co odpowiednia klasa AppWidgetProvider.

Określanie częstotliwości aktualizowania widżetu

Widżety są aktualizowane okresowo w zależności od wartości atrybutu updatePeriodMillis. Widżet może się aktualizować w odpowiedzi na interakcję użytkownika, komunikaty lub jedno i drugie.

Aktualizuj okresowo

Możesz kontrolować częstotliwość okresowych aktualizacji, określając wartość AppWidgetProviderInfo.updatePeriodMillis w pliku XML appwidget-provider. Każda aktualizacja uruchamia metodę AppWidgetProvider.onUpdate(), w której możesz umieścić kod aktualizujący widżet. Jeśli jednak widżet musi wczytywać dane asynchronicznie lub aktualizować się dłużej niż 10 sekund, po 10 sekundach system uzna, że BroadcastReceiver nie odpowiada, dlatego rozważ zastosowanie alternatywnych aktualizacji odbiornika opisanych w następnej sekcji.

updatePeriodMillis nie obsługuje wartości krótszych niż 30 minut. Jeśli jednak chcesz wyłączyć okresowe aktualizacje, możesz wpisać 0.

Możesz zezwolić użytkownikom na dostosowywanie częstotliwości aktualizacji w konfiguracji. Przykładowo mogą chcieć, aby pasek giełdowy aktualizował się co 15 minut lub tylko 4 razy dziennie. W takim przypadku ustaw updatePeriodMillis na 0 i użyj WorkManager.

Aktualizacja w odpowiedzi na interakcję użytkownika

Oto kilka zalecanych sposobów aktualizowania widżetu w zależności od interakcji użytkownika:

  • Z poziomu aktywności w aplikacji: wywołaj bezpośrednio AppWidgetManager.updateAppWidget w odpowiedzi na interakcję użytkownika, np. kliknięcie.

  • Z interakcji zdalnych, takich jak powiadomienie lub widżet aplikacji: utwórz PendingIntent, a potem zaktualizuj widżet z wywołanych Activity, Broadcast lub Service. Możesz wybrać własny priorytet. Jeśli na przykład wybierzesz pole Broadcast dla elementu PendingIntent, możesz wybrać transmisję na pierwszym planie, aby nadać jej priorytet BroadcastReceiver.

Aktualizacja w odpowiedzi na transmitowane wydarzenie

Przykładem transmitowanego wydarzenia, które wymaga aktualizacji widżetu, jest zrobienie zdjęcia przez użytkownika. W tej sytuacji możesz zaktualizować widżet po wykryciu nowego zdjęcia.

Możesz zaplanować zadanie za pomocą funkcji JobScheduler i określić transmisję jako aktywator przy użyciu metody JobInfo.Builder.addTriggerContentUri.

Możesz również zarejestrować adres BroadcastReceiver na potrzeby transmisji, na przykład nasłuchiwać ACTION_LOCALE_CHANGED. Ponieważ jednak zużywa to zasoby urządzenia, korzystaj z tej funkcji ostrożnie i nasłuchuj tylko konkretnego komunikatu. W związku z wprowadzeniem ograniczeń transmisji na Androidzie 7.0 (poziom interfejsu API 24) i Androidzie 8.0 (poziom interfejsu API 26) aplikacje nie mogą rejestrować niejawnych komunikatów w plikach manifestu (z pewnymi wyjątkami).

Uwagi na temat aktualizowania widżetu z BroadcastReceiver

Jeśli widżet jest aktualizowany z elementu BroadcastReceiver, w tym z elementu AppWidgetProvider, pamiętaj o tych kwestiach związanych z czasem trwania i priorytetem aktualizacji widżetu.

Czas trwania aktualizacji

Z reguły system zezwala odbiornikom, które zwykle działają w głównym wątku aplikacji, przez maksymalnie 10 sekund, zanim uzna, że nie odpowiada, i wywoła błąd Aplikacja nie odpowiada (ANR). Jeśli aktualizacja widżetu trwa dłużej, rozważ inne możliwości:

  • Zaplanuj zadanie za pomocą funkcji WorkManager.

  • Zapewnij odbiorcy więcej czasu, używając metody goAsync. Dzięki temu odbiorcy będą wykonywać 30 sekund.

Więcej informacji znajdziesz w artykule Kwestie związane z bezpieczeństwem i sprawdzone metody.

Priorytet aktualizacji

Domyślnie transmisje – w tym te przeprowadzone za pomocą AppWidgetProvider.onUpdate – są uruchamiane w tle. Oznacza to, że przeciążone zasoby systemowe mogą powodować opóźnienie w wywoływaniu odbiornika. Aby nadać transmisji priorytetową, ustaw ją jako proces na pierwszym planie.

Na przykład dodaj flagę Intent.FLAG_RECEIVER_FOREGROUND do elementu Intent przekazywanego do PendingIntent.getBroadcast, gdy użytkownik kliknie określoną część widżetu.

Tworzenie dokładnych podglądów z uwzględnieniem elementów dynamicznych

Rys. 1. Podgląd widżetu bez elementów listy.

W tej sekcji opisujemy zalecane podejście do wyświetlania wielu elementów w podglądzie widżetu z widokiem kolekcji – czyli w widżetach używających ListView, GridView lub StackView.

Jeśli Twój widżet korzysta z jednego z tych widoków, utworzenie skalowalnego podglądu przez bezpośrednie podanie rzeczywistego układu widżetu pogarsza komfort korzystania, gdy podgląd widżetu nie wyświetla żadnych elementów. Dzieje się tak, ponieważ dane widoku kolekcji są ustawiane dynamicznie w czasie działania i wyglądają podobnie jak na ilustracji 1.

Aby podgląd widżetów z widokami kolekcji był prawidłowo wyświetlany w selektorze widżetów, zalecamy zachowanie osobnego pliku układu przeznaczonego tylko na podgląd. Ten oddzielny plik układu zawiera rzeczywisty układ widżetu i zastępczy widok kolekcji z fałszywymi elementami. Możesz na przykład imitować element ListView, podając obiekt zastępczy LinearLayout z kilkoma fałszywymi elementami listy.

Aby pokazać przykład dla elementu ListView, zacznij od osobnego pliku układu:

// res/layout/widget_preview.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:background="@drawable/widget_background"
   android:orientation="vertical">

    // Include the actual widget layout that contains ListView.
    <include
        layout="@layout/widget_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    // The number of fake items you include depends on the values you provide
    // for minHeight or targetCellHeight in the AppWidgetProviderInfo
    // definition.

    <TextView android:text="@string/fake_item1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginVertical="?attr/appWidgetInternalPadding" />

    <TextView android:text="@string/fake_item2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginVertical="?attr/appWidgetInternalPadding" />

</LinearLayout>

Określ plik układu podglądu, podając atrybut previewLayout metadanych AppWidgetProviderInfo. Nadal możesz określić rzeczywisty układ widżetu dla atrybutu initialLayout i używać rzeczywistego układu widżetu podczas tworzenia obiektu RemoteViews w czasie działania.

<appwidget-provider
    previewLayout="@layout/widget_previe"
    initialLayout="@layout/widget_view" />

Złożone elementy listy

Przykład w poprzedniej sekcji przedstawia fałszywe elementy listy, bo elementy listy są obiektami TextView. Dostarczenie fałszywych elementów może być bardziej skomplikowane, jeśli elementy mają złożone układy.

Weźmy pod uwagę element listy zdefiniowany w zasadzie widget_list_item.xml i składający się z 2 obiektów TextView:

<LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    <TextView android:id="@id/title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/fake_title" />

    <TextView android:id="@id/content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/fake_content" />
</LinearLayout>

Aby podać fałszywe pozycje listy, możesz dodać układ kilka razy, ale wtedy każdy element listy będzie taki sam. Aby podać unikalne elementy listy, wykonaj te czynności:

  1. Utwórz zestaw atrybutów dla wartości tekstowych:

    <resources>
        <attr name="widgetTitle" format="string" />
        <attr name="widgetContent" format="string" />
    </resources>
    
  2. Użyj tych atrybutów, aby ustawić tekst:

    <LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    
        <TextView android:id="@id/title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="?widgetTitle" />
    
        <TextView android:id="@id/content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="?widgetContent" />
    </LinearLayout>
    
  3. Utwórz dowolną liczbę stylów do podglądu. Zdefiniuj ponownie wartości w każdym stylu:

    <resources>
    
        <style name="Theme.Widget.ListItem">
            <item name="widgetTitle"></item>
            <item name="widgetContent"></item>
        </style>
        <style name="Theme.Widget.ListItem.Preview1">
            <item name="widgetTitle">Fake Title 1</item>
            <item name="widgetContent">Fake content 1</item>
        </style>
        <style name="Theme.Widget.ListItem.Preview2">
            <item name="widgetTitle">Fake title 2</item>
            <item name="widgetContent">Fake content 2</item>
        </style>
    
    </resources>
    
  4. Zastosuj style do fałszywych elementów w układzie podglądu:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
       android:layout_width="match_parent"
       android:layout_height="wrap_content" ...>
    
        <include layout="@layout/widget_view" ... />
    
        <include layout="@layout/widget_list_item"
            android:theme="@style/Theme.Widget.ListItem.Preview1" />
    
        <include layout="@layout/widget_list_item"
            android:theme="@style/Theme.Widget.ListItem.Preview2" />
    
    </LinearLayout>