Tworzenie zaawansowanego widżetu

Wypróbuj Compose
Jetpack Compose to zalecany zestaw narzędzi interfejsu na Androida. Dowiedz się, jak tworzyć widżety za pomocą interfejsów API w stylu Compose.

Na tej stronie znajdziesz zalecane metody tworzenia bardziej zaawansowanego widżetu, który zapewni użytkownikom większy komfort.

Optymalizacje aktualizowania treści widżetu

Aktualizowanie treści widżetu może być kosztowne pod względem obliczeniowym. Aby oszczędzać baterię, zoptymalizuj typ, częstotliwość i czas aktualizacji.

Rodzaje aktualizacji widżetów

Widżet można zaktualizować na 3 sposoby: w całości, częściowo lub (w przypadku widżetu kolekcji) przez odświeżenie danych. Każda z nich wiąże się z innymi kosztami obliczeniowymi i konsekwencjami.

Poniżej opisujemy każdy typ aktualizacji i podajemy przykłady kodu.

  • Pełna aktualizacja: wywołaj AppWidgetManager.updateAppWidget(int, android.widget.RemoteViews), aby w pełni zaktualizować widżet. Zastąpi to wcześniej podany adres RemoteViews nowym adresem RemoteViews. Jest to najbardziej wymagająca obliczeniowo aktualizacja.

    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);
  • Aktualizacja częściowa: wywołanie AppWidgetManager.partiallyUpdateAppWidget w celu zaktualizowania części widżetu. Spowoduje to połączenie nowego urządzenia RemoteViews z wcześniej podanym urządzeniem RemoteViews. Ta metoda jest ignorowana, jeśli widżet nie otrzyma co najmniej jednej pełnej aktualizacji za pomocą funkcji 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 RemoteViewsFactory.onDataSetChanged. W tym czasie w widżecie będą wyświetlane stare dane. Dzięki tej metodzie możesz bezpiecznie wykonywać kosztowne 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, o ile ma ona ten sam identyfikator UID co odpowiednia klasa AppWidgetProvider.

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

Widżety są okresowo aktualizowane w zależności od wartości podanej w atrybucie updatePeriodMillis. Widżet może się aktualizować w odpowiedzi na interakcję użytkownika, rozsyłać aktualizacje lub robić jedno i drugie.

Okresowe aktualizacje

Częstotliwość okresowej aktualizacji możesz kontrolować, określając wartość elementu AppWidgetProviderInfo.updatePeriodMillis w pliku XML appwidget-provider. Każda aktualizacja wywołuje 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 aktualizacja trwa dłużej niż 10 sekund, rozważ alternatywne rozwiązania dotyczące aktualizacji odbiornika transmisji opisane w dalszej części, ponieważ po 10 sekundach system uznaje BroadcastReceiver za nieodpowiadający.

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

Możesz zezwolić użytkownikom na dostosowywanie częstotliwości aktualizacji w konfiguracji. Mogą na przykład chcieć, aby notowania giełdowe były aktualizowane co 15 minut lub tylko 4 razy dziennie. W takim przypadku ustaw wartość updatePeriodMillis na 0 i użyj WorkManager.

Aktualizacja w odpowiedzi na interakcję użytkownika

Oto kilka zalecanych sposobów aktualizowania widżetu na podstawie interakcji użytkownika:

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

  • W przypadku interakcji zdalnych, np. powiadomienia lub widżetu aplikacji: utwórz PendingIntent, a następnie zaktualizuj widżet z wywołanego Activity, Broadcast lub Service. Możesz wybrać własny priorytet. Jeśli na przykład wybierzesz Broadcast dla PendingIntent, możesz wybrać transmisję na pierwszym planie, aby nadać priorytet BroadcastReceiver.

Aktualizacja w odpowiedzi na wydarzenie transmisji

Przykładem transmitowanego wydarzenia, które wymaga aktualizacji widżetu, jest zrobienie zdjęcia przez użytkownika. W tym przypadku chcesz zaktualizować widżet, gdy wykryte zostanie nowe zdjęcie.

Możesz zaplanować zadanie za pomocą JobScheduler i określić transmisję jako wyzwalacz, używając metody JobInfo.Builder.addTriggerContentUri.

Możesz też zarejestrować BroadcastReceiver na potrzeby transmisji, np. słuchanie ACTION_LOCALE_CHANGED. Ponieważ jednak zużywa to zasoby urządzenia, korzystaj z tej funkcji ostrożnie i słuchaj tylko konkretnej transmisji. W Androidzie 7.0 (poziom interfejsu API 24) i Androidzie 8.0 (poziom interfejsu API 26) wprowadzono ograniczenia dotyczące transmisji, w związku z czym aplikacje nie mogą rejestrować w swoich plikach manifestu transmisji niejawnych, z pewnymi wyjątkami.

Na co zwrócić uwagę podczas aktualizowania widżetu z poziomu BroadcastReceiver

Jeśli widżet jest aktualizowany z urządzenia BroadcastReceiver, w tym z urządzenia AppWidgetProvider, pamiętaj o tych kwestiach dotyczących czasu trwania i priorytetu aktualizacji widżetu.

Czas trwania aktualizacji

Z reguły system pozwala odbiornikom transmisji, które zwykle działają w głównym wątku aplikacji, działać przez maksymalnie 10 sekund, zanim uzna je za niereagujące i wywoła błąd Aplikacja nie odpowiada (ANR). Aby uniknąć blokowania wątku głównego podczas obsługi transmisji, użyj metody goAsync. Jeśli aktualizacja widżetu trwa dłużej, rozważ zaplanowanie zadania za pomocą WorkManager.

Caution: Any work you do here blocks further broadcasts until it completes,
so it can slow the receiving of later events.

Więcej informacji znajdziesz w artykule Wskazówki i sprawdzone metody dotyczące bezpieczeństwa.

Priorytet aktualizacji

Domyślnie transmisje, w tym te tworzone za pomocą AppWidgetProvider.onUpdate, działają jako procesy w tle. Oznacza to, że przeładowanie zasobów systemu może spowodować opóźnienie wywołania odbiornika transmisji. Aby nadać transmisji priorytet, ustaw ją jako proces na pierwszym planie.

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

Tworzenie dokładnych podglądów zawierających elementy dynamiczne

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

W tej sekcji znajdziesz zalecane podejście do wyświetlania wielu elementów w podglądzie widżetu z widokiem kolekcji, czyli widżetu, który używa komponentu ListView, GridView lub StackView.

Jeśli widżet korzysta z jednego z tych widoków, utworzenie skalowalnego podglądu przez bezpośrednie podanie rzeczywistego układu widżetu pogorszy komfort użytkowania, gdy podgląd widżetu nie będzie wyświetlać żadnych elementów. Dzieje się tak, ponieważ dane widoku kolekcji są ustawiane dynamicznie w czasie działania i wyglądają podobnie do obrazu pokazanego na rysunku 1.

Aby podglądy widżetów z widokami kolekcji wyświetlały się prawidłowo w selektorze widżetów, zalecamy używanie osobnego pliku układu przeznaczonego tylko do podglądu. Ten osobny plik układu zawiera rzeczywisty układ widżetu i widok kolekcji z obiektami zastępczymi. Możesz na przykład naśladować ListView, podając obiekt zastępczy LinearLayout z kilkoma fałszywymi elementami listy.

Aby zilustrować przykład 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>

Podaj plik układu podglądu, gdy podajesz atrybut previewLayout metadanych AppWidgetProviderInfo. Nadal określasz rzeczywisty układ widżetu dla atrybutu initialLayout i używasz rzeczywistego układu widżetu podczas tworzenia RemoteViews w czasie działania.

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

Złożone elementy listy

W przykładzie w poprzedniej sekcji podano przykładowe elementy listy, ponieważ elementy listy są obiektami TextView. W przypadku złożonych układów dostarczanie fałszywych elementów może być trudniejsze.

Rozważmy element listy zdefiniowany w widget_list_item.xml, który składa 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 elementy listy, możesz uwzględnić układ wiele razy, ale spowoduje to, że każdy element listy będzie identyczny. 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. Aby ustawić tekst, użyj tych atrybutów:

    <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 tyle stylów, ile jest potrzebnych 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 przykładowych produktó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>