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 dotyczące aktualizowania zawartości widżetów

Aktualizowanie zawartości widżetów może być kosztowne. Aby oszczędzać baterię, zoptymalizuj typ, częstotliwość i czas aktualizacji.

Rodzaje aktualizacji widżetów

Widżet można zaktualizować na 3 sposoby: przez pełną aktualizację, częściową aktualizację oraz, w przypadku widżetu kolekcji, odświeżenie danych. Każdy z nich wiąże się z innymi kosztami i konsekwencjami.

Poniżej opisujemy poszczególne typy aktualizacji i fragmenty kodu 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 najbardziej kosztowna 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);
    
  • Częściowa aktualizacja: wywołaj AppWidgetManager.partiallyUpdateAppWidget, aby zaktualizować części widżetu. Spowoduje to scalenie nowego elementu RemoteViews z podaną wcześniej wartością RemoteViews. Ta metoda jest ignorowana, jeśli widżet nie otrzymał co najmniej 1 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. Powoduje to aktywowanie RemoteViewsFactory.onDataSetChanged. Do tego czasu stare dane są wyświetlane w widżecie. Za pomocą tej metody 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łać te metody z dowolnego miejsca w aplikacji, o ile ma ona ten sam identyfikator UID co odpowiednia klasa AppWidgetProvider.

Określanie, jak często aktualizować widżet

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, rozpowszechnianie aktualizacji lub jedno i drugie.

Aktualizuj okresowo

Częstotliwość aktualizacji okresowych możesz kontrolować, określając wartość AppWidgetProviderInfo.updatePeriodMillis w pliku XML appwidget-provider. Każda aktualizacja aktywuje metodę AppWidgetProvider.onUpdate(), która pozwala umieścić kod, aby zaktualizować widżet. Jeśli jednak widżet musi wczytywać dane asynchronicznie lub aktualizować się przez ponad 10 sekund, ponieważ po 10 sekundach system uzna BroadcastReceiver za niereagujący, weź pod uwagę alternatywne alternatywy dla aktualizacji odbiornika transmisji opisane w kolejnej 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. Może na przykład określić, czy notowania giełdowe mają być aktualizowane co 15 minut lub tylko 4 razy dziennie. W tym przypadku ustaw updatePeriodMillis na 0 i użyj zamiast niego WorkManager.

Aktualizacja w odpowiedzi na interakcję użytkownika

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

  • Z poziomu aktywności w aplikacji: bezpośrednie wywołanie metody 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 następnie zaktualizuj widżet z wywołanych zdarzeń Activity, Broadcast lub Service. Możesz wybrać własny priorytet. Jeśli na przykład wybierzesz typ Broadcast dla funkcji PendingIntent, możesz wybrać transmisję na pierwszym planie, aby nadać priorytet BroadcastReceiver.

Aktualizacja w odpowiedzi na transmisję

Przykładem transmisji, która wymaga zaktualizowania widżetu, jest zrobienie zdjęcia przez użytkownika. W takiej sytuacji trzeba aktualizować widżet po wykryciu nowego zdjęcia.

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

Możesz też zarejestrować BroadcastReceiver dla transmisji, na przykład słuchanie ACTION_LOCALE_CHANGED. Ponieważ jednak zużywa to zasoby urządzenia, zachowaj ostrożność i nasłuchuj tylko konkretnej transmisji. Po wprowadzeniu ograniczeń transmisji na Androidzie 7.0 (poziom interfejsu API 24) i Android 8.0 (poziom interfejsu API 26) aplikacje nie mogą rejestrować niejawnych transmisji w plikach manifestu (z pewnymi wyjątkami).

O czym należy pamiętać podczas aktualizowania widżetu z BroadcastReceivedr

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

Czas trwania aktualizacji

Zasadniczo system zezwala odbiornikom transmisji, które zwykle działają w głównym wątku aplikacji, na działanie przez maksymalnie 10 sekund, zanim stwierdzi, że nie odpowiadają, 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 w: WorkManager.

  • Zapewnij odbiorcy więcej czasu, korzystając z metody goAsync. Dzięki temu odbiorniki będą mogły działać przez 30 sekund.

Więcej informacji znajdziesz w artykule Uwagi na temat bezpieczeństwa i sprawdzone metody.

Priorytet aktualizacji

Domyślnie komunikaty – w tym te utworzone 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ć priorytet transmisji, powinna być ona procesem na pierwszym planie.

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

Tworzenie dokładnych podglądów z elementami dynamicznymi

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

W tej sekcji omawiamy zalecane sposoby wyświetlania wielu elementów w podglądzie widżetu z widokiem kolekcji, czyli widżetem, który korzysta z ListView, GridView lub StackView.

Jeśli Twój widżet korzysta z jednego z tych widoków, utworzenie skalowalnego podglądu przez bezpośrednie udostępnienie rzeczywistego układu widżetu pogarsza działanie, gdy w podglądzie widżetu nie ma żadnych elementów. Dzieje się tak, ponieważ dane widoku kolekcji są ustawiane dynamicznie w czasie działania i wyglądają podobnie do obrazu przedstawionego na ilustracji 1.

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

Aby zilustrować przykład 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 podczas podawania atrybutu previewLayout metadanych AppWidgetProviderInfo. Nadal jednak określasz rzeczywisty układ widżetu dla atrybutu initialLayout, a podczas tworzenia elementu RemoteViews w czasie działania korzystasz z rzeczywistego układu widżetu.

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

Złożone elementy listy

W przykładzie w poprzedniej sekcji przedstawiliśmy fałszywe elementy listy, ponieważ są one obiektami TextView. Jeśli elementy mają złożone układy, podanie fałszywych elementów może być trudniejsze.

Weźmy pod uwagę element listy zdefiniowany w zasadzie 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 podawać fałszywe elementy listy, możesz stosować układ wielokrotnie, ale sprawi to, że każdy element będzie taki sam. Aby dodać 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 tyle stylów, ile potrzebujesz 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. 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>