Tworzenie zaawansowanego widżetu

Na tej stronie znajdziesz informacje o sprawdzonych metodach tworzenia bardziej zaawansowanego widżetu, który zapewni użytkownikom lepsze wrażenia.

Optymalizacja aktualizacji treści widżetu

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

Rodzaje aktualizacji widżetów

Widżet możesz zaktualizować na 3 sposoby: w pełni, częściowo lub, w przypadku widżetu kolekcji, odświeżyć dane. Każdy z nich wiąże się z różnymi kosztami i konsekwencjami obliczeniowymi.

Poniżej opisujemy poszczególne typy aktualizacji i zamieszczamy odpowiednie fragmenty kodu.

  • Pełna aktualizacja: wywołaj AppWidgetManager.updateAppWidget(int, android.widget.RemoteViews), aby w pełni zaktualizować widżet. Spowoduje to zastąpienie wcześniej przesłanego pliku RemoteViews nowym plikiem RemoteViews. To najbardziej czasochłonne z aktualizacji.

    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łanie AppWidgetManager.partiallyUpdateAppWidget służy do aktualizowania części widżetu. Spowoduje to połączenie nowego RemoteViews z dostarczonym wcześniej RemoteViews. Ta metoda jest ignorowana, jeśli widżet nie otrzyma co najmniej 1 pełnego uaktualnienia za pomocą 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 metodę AppWidgetManager.notifyAppWidgetViewDataChanged, aby unieważnić dane widoku kolekcji na widżecie. Spowoduje to uruchomienie RemoteViewsFactory.onDataSetChanged. W międzyczasie w widżecie będą wyświetlane stare dane. Za pomocą tej metody możesz bezpiecznie wykonywać kosztowne zadania w sposób synchroniczny.

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

Te metody możesz wywoływać z dowolnego miejsca w aplikacji, o ile ma ona ten sam identyfikator UID co odpowiadająca jej klasa AppWidgetProvider.

Określanie częstotliwości odświeżania widżetu

Widgety są okresowo aktualizowane w zależności od wartości podanej dla atrybutu updatePeriodMillis. Widget może się aktualizować w reakcji na interakcję z użytkownikiem, aktualizacje rozsyłane do wielu urządzeń lub obie te czynności.

Aktualizuj okresowo

Częstotliwość okresowych aktualizacji możesz kontrolować, określając wartość elementu AppWidgetProviderInfo.updatePeriodMillis w pliku XML appwidget-provider. Każda aktualizacja powoduje wywołanie metody AppWidgetProvider.onUpdate(), w której możesz umieścić kod służący do aktualizowania widżetu. Jeśli jednak widżet musi wczytywać dane asynchronicznie lub jego aktualizacja trwa dłużej niż 10 sekund, rozważ alternatywy dla aktualizacji odbiornika transmisji opisane w następującej sekcji, ponieważ po 10 sekundach system uzna, że BroadcastReceiver nie odpowiada.

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

Możesz zezwolić użytkownikom na zmianę częstotliwości aktualizacji w ramach konfiguracji. Mogą na przykład chcieć, aby ticker akcji był aktualizowany co 15 minut lub tylko 4 razy dziennie. W takim przypadku ustaw wartość updatePeriodMillis na 0, a zamiast niej użyj operatora 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: wywołaj bezpośrednio funkcję AppWidgetManager.updateAppWidget w odpowiedzi na działanie użytkownika, np. kliknięcie.

  • Z dalszych interakcji, takich jak powiadomienie lub widżet 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ć komentarz na pierwszym planie, aby nadać BroadcastReceiver priorytet.

Aktualizacja w odpowiedzi na zdarzenie transmisji

Przykładem zdarzenia przesyłania strumieniowego, które wymaga zaktualizowania widżetu, jest zrobienie zdjęcia przez użytkownika. W tym przypadku chcesz zaktualizować widget, gdy zostanie wykryte nowe zdjęcie.

Możesz zaplanować zadanie za pomocą funkcji JobScheduler i użyć jako wyzwalacza transmisji strumieniowej, używając metody JobInfo.Builder.addTriggerContentUri.

Możesz też zarejestrować BroadcastReceiver dla transmisji, np. słuchanie ACTION_LOCALE_CHANGED. Ponieważ jednak funkcja ta zużywa zasoby urządzenia, należy z niej korzystać ostrożnie i słuchać tylko konkretnego programu. W Androidzie 7.0 (poziom interfejsu API 24) i Androidzie 8.0 (poziom interfejsu API 26) wprowadzono ograniczenia dotyczące transmisji danych, które uniemożliwiają rejestrowanie w plikach manifestu niejawnych transmisji danych (z pewnymi wyjątkami).

O czym należy pamiętać 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

Zazwyczaj 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 niedziałające i wyświetli błąd Aplikacja nie odpowiada (ANR). Jeśli aktualizacja widżetu trwa dłużej, rozważ te opcje:

  • Zaplanuj zadanie za pomocą WorkManager.

  • Daj odbiorcy więcej czasu, korzystając z metody goAsync. Dzięki temu odbiorcy mogą wykonywać operacje przez 30 sekund.

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

Priorytet aktualizacji

Domyślnie transmisje, w tym te prowadzone za pomocą AppWidgetProvider.onUpdate, są uruchamiane jako procesy w tle. Oznacza to, że przeciążone zasoby systemu mogą spowodować opóźnienie wywołania odbiornika transmisji. Aby nadawanie miało priorytet, ustaw je jako proces na pierwszym planie.

Możesz na przykład dodać do parametru Intent przekazywanego do parametru PendingIntent.getBroadcast flagę Intent.FLAG_RECEIVER_FOREGROUND, która jest wywoływana, gdy użytkownik kliknie określony element widżetu.

Tworzenie dokładnych podglądów, które zawierają elementy dynamiczne

Ilustracja 1. Podgląd widżetu, który nie wyświetla żadnych elementów listy.

W tej sekcji znajdziesz wyjaśnienie zalecanego podejścia do wyświetlania wielu elementów w podglądzie podglądu widżetu z widokiem kolekcji, czyli widżetu, który używa tagu ListView, GridView lub StackView.

Jeśli widżet używa jednego z tych widoków, tworzenie skalowanego podglądu przez bezpośrednie udostępnianie rzeczywistego układu widżetu pogarsza wrażenia użytkownika, gdy podgląd widżetu nie wyświetla żadnych elementów. Dzieje się tak, ponieważ dane widoku kolekcji są ustawiane dynamicznie w czasie wykonywania programu i wyglądają podobnie do obrazu pokazanego na rysunku 1.

Aby w selektorze widżetów prawidłowo wyświetlały się podglądy widżetów z widokami kolekcji, zalecamy utrzymanie osobnego pliku układu przeznaczonego tylko do podglądu. Ten oddzielny plik układu zawiera rzeczywisty układ widżetu i widok kolekcji z miejscem zastępczym z fałszywymi elementami. Możesz na przykład zasymulować obiekt 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>

Podczas podawania atrybutu previewLayout w metadanych AppWidgetProviderInfo określ plik układu podglądu. Nadal określasz rzeczywisty układ widżetu dla atrybutu initialLayout i używasz rzeczywistego układu widżetu podczas tworzenia elementu RemoteViews w czasie wykonywania.

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

Elementy listy złożone

Przykład w poprzedniej sekcji zawiera fałszywe elementy listy, ponieważ są to obiekty TextView. Podanie fałszywych elementów może być trudniejsze, jeśli są to złożone układy.

Weź pod uwagę element listy zdefiniowany w elementach 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 użyć układu kilka razy, ale spowoduje to, że wszystkie elementy będą identyczne. Aby przesłać 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 wyświetlenia 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. Zastosowanie stylów 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>