Erweitertes Widget erstellen

Compose ausprobieren
Jetpack Compose ist das empfohlene UI-Toolkit für Android. Informationen zum Erstellen von Widgets mit APIs im Compose-Stil.

Auf dieser Seite werden empfohlene Vorgehensweisen zum Erstellen eines komplexeren Widgets beschrieben, um die Nutzerfreundlichkeit zu verbessern.

Optimierungen für das Aktualisieren von Widget-Inhalten

Das Aktualisieren von Widget-Inhalten kann rechenintensiv sein. Um den Akkuverbrauch zu senken, sollten Sie den Aktualisierungstyp, die Häufigkeit und den Zeitpunkt optimieren.

Arten von Widget-Updates

Es gibt drei Möglichkeiten, ein Widget zu aktualisieren: eine vollständige Aktualisierung, eine Teilaktualisierung und, im Fall eines Sammlungs-Widgets, eine Datenaktualisierung. Jede hat unterschiedliche Rechenkosten und Auswirkungen.

Im Folgenden werden die einzelnen Aktualisierungstypen beschrieben und es werden Code-Snippets für jeden Typ bereitgestellt.

  • Vollständiges Update:Rufen Sie AppWidgetManager.updateAppWidget(int, android.widget.RemoteViews) auf, um das Widget vollständig zu aktualisieren. Dadurch wird die zuvor angegebene RemoteViews durch eine neue RemoteViews ersetzt. Dies ist die rechenintensivste Aktualisierung.

    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);
  • Teilaktualisierung:Rufen Sie AppWidgetManager.partiallyUpdateAppWidget auf, um Teile des Widgets zu aktualisieren. Dadurch wird die neue RemoteViews mit der zuvor angegebenen RemoteViews zusammengeführt. Diese Methode wird ignoriert, wenn ein Widget nicht mindestens ein vollständiges Update über updateAppWidget(int[], RemoteViews) erhält.

    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);
  • Aktualisierung von Sammlungsdaten:Rufen Sie AppWidgetManager.notifyAppWidgetViewDataChanged auf, um die Daten einer Sammlung in Ihrem Widget zu invalidieren. Dadurch wird RemoteViewsFactory.onDataSetChanged ausgelöst. In der Zwischenzeit werden die alten Daten im Widget angezeigt. Mit dieser Methode können Sie rechenintensive Aufgaben sicher synchron ausführen.

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

Sie können diese Methoden von überall in Ihrer App aufrufen, sofern die App dieselbe UID wie die entsprechende AppWidgetProvider-Klasse hat.

Festlegen, wie oft ein Widget aktualisiert werden soll

Widgets werden regelmäßig aktualisiert, je nach dem Wert, der für das Attribut updatePeriodMillis angegeben ist. Das Widget kann als Reaktion auf Nutzerinteraktionen, Broadcast-Updates oder beides aktualisiert werden.

Regelmäßig aktualisieren

Sie können die Häufigkeit der regelmäßigen Aktualisierung steuern, indem Sie in der appwidget-provider-XML einen Wert für AppWidgetProviderInfo.updatePeriodMillis angeben. Jede Aktualisierung löst die Methode AppWidgetProvider.onUpdate() aus. Dort können Sie den Code zum Aktualisieren des Widgets platzieren. Wenn Ihr Widget Daten asynchron laden muss oder die Aktualisierung länger als 10 Sekunden dauert, sollten Sie jedoch die Alternativen für Broadcast-Receiver-Updates in einem der folgenden Abschnitte in Betracht ziehen, da das System nach 10 Sekunden davon ausgeht, dass ein BroadcastReceiver nicht reagiert.

updatePeriodMillis unterstützt keine Werte unter 30 Minuten. Wenn Sie regelmäßige Updates deaktivieren möchten, können Sie jedoch 0 angeben.

Sie können Nutzern erlauben, die Häufigkeit von Aktualisierungen in einer Konfiguration anzupassen. Ein Nutzer möchte beispielsweise, dass ein Aktien-Ticker alle 15 Minuten oder nur viermal am Tag aktualisiert wird. Setzen Sie in diesem Fall updatePeriodMillis auf 0 und verwenden Sie stattdessen WorkManager.

Aktualisierung als Reaktion auf eine Nutzerinteraktion

Hier sind einige empfohlene Möglichkeiten, das Widget basierend auf Nutzerinteraktionen zu aktualisieren:

  • Über eine Aktivität der App:Rufen Sie AppWidgetManager.updateAppWidget direkt als Reaktion auf eine Nutzerinteraktion auf, z. B. wenn ein Nutzer tippt.

  • Über Remote-Interaktionen wie eine Benachrichtigung oder ein App-Widget:Erstellen Sie ein PendingIntent und aktualisieren Sie dann das Widget über das aufgerufene Activity, Broadcast oder Service. Sie können Ihre eigene Priorität auswählen. Wenn Sie beispielsweise eine Broadcast für die PendingIntent auswählen, können Sie eine Vordergrundübertragung auswählen, um der BroadcastReceiver Priorität zu geben.

Als Reaktion auf ein Broadcast-Event aktualisieren

Ein Beispiel für ein Broadcast-Ereignis, das eine Aktualisierung des Widgets erfordert, ist, wenn der Nutzer ein Foto aufnimmt. In diesem Fall soll das Widget aktualisiert werden, wenn ein neues Foto erkannt wird.

Sie können einen Job mit JobScheduler planen und eine Broadcast als Trigger mit der Methode JobInfo.Builder.addTriggerContentUri angeben.

Sie können auch ein BroadcastReceiver für die Übertragung registrieren, z. B. ACTION_LOCALE_CHANGED. Da dies jedoch Geräteressourcen beansprucht, sollten Sie diese Funktion nur mit Bedacht verwenden und nur den jeweiligen Broadcast anhören. Mit der Einführung von Broadcast-Einschränkungen in Android 7.0 (API-Level 24) und Android 8.0 (API-Level 26) können Apps keine impliziten Broadcasts in ihren Manifesten registrieren, mit bestimmten Ausnahmen.

Hinweise zum Aktualisieren eines Widgets über einen BroadcastReceiver

Wenn das Widget über ein BroadcastReceiver aktualisiert wird, einschließlich AppWidgetProvider, gelten die folgenden Überlegungen zur Dauer und Priorität einer Widget-Aktualisierung.

Dauer des Updates

In der Regel lässt das System Broadcast-Receiver, die normalerweise im Hauptthread der App ausgeführt werden, bis zu 10 Sekunden lang laufen, bevor es sie als nicht reagierend betrachtet und einen Application Not Responding-Fehler (ANR) auslöst. Verwenden Sie die Methode goAsync, um zu vermeiden, dass der Hauptthread während der Verarbeitung des Broadcasts blockiert wird. Wenn die Aktualisierung des Widgets länger dauert, sollten Sie eine Aufgabe mit WorkManager planen.

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

Weitere Informationen finden Sie unter Sicherheitsaspekte und Best Practices.

Priorität des Updates

Standardmäßig werden Broadcasts, auch solche, die mit AppWidgetProvider.onUpdate erstellt wurden, als Hintergrundprozesse ausgeführt. Das bedeutet, dass überlastete Systemressourcen zu einer Verzögerung beim Aufrufen des Broadcast-Receivers führen können. Wenn Sie die Übertragung priorisieren möchten, machen Sie sie zu einem Vordergrundprozess.

Fügen Sie beispielsweise das Flag Intent.FLAG_RECEIVER_FOREGROUND zum Intent hinzu, das an PendingIntent.getBroadcast übergeben wird, wenn der Nutzer auf einen bestimmten Teil des Widgets tippt.

Genaue Vorschauen mit dynamischen Elementen erstellen

Abbildung 1 : Eine Widget-Vorschau ohne Listenelemente.

In diesem Abschnitt wird die empfohlene Vorgehensweise zum Anzeigen mehrerer Elemente in einer Widget-Vorschau für ein Widget mit einer Sammlungsansicht beschrieben, d. h. ein Widget, das ListView, GridView oder StackView verwendet.

Wenn in Ihrem Widget eine dieser Ansichten verwendet wird, wird die Nutzerfreundlichkeit beeinträchtigt, wenn in der Widget-Vorschau keine Elemente angezeigt werden, weil Sie eine skalierbare Vorschau erstellen, indem Sie das tatsächliche Widget-Layout direkt angeben. Das liegt daran, dass die Daten für die Sammlung dynamisch zur Laufzeit festgelegt werden. Sie sehen ähnlich aus wie das Bild in Abbildung 1.

Damit Vorschauen von Widgets mit Sammlungsansichten in der Widget-Auswahl richtig angezeigt werden, empfehlen wir, eine separate Layoutdatei nur für die Vorschau zu verwenden. Diese separate Layoutdatei enthält das eigentliche Widget-Layout und eine Platzhalter-Sammlungsansicht mit gefälschten Elementen. Sie können beispielsweise ein ListView simulieren, indem Sie einen Platzhalter LinearLayout mit mehreren gefälschten Listenelementen angeben.

Hier ist ein Beispiel für eine ListView, das mit einer separaten Layoutdatei beginnt:

// 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>

Geben Sie die Vorschau-Layoutdatei an, wenn Sie das Attribut previewLayout der AppWidgetProviderInfo-Metadaten angeben. Sie geben weiterhin das tatsächliche Widget-Layout für das Attribut initialLayout an und verwenden das tatsächliche Widget-Layout, wenn Sie zur Laufzeit ein RemoteViews erstellen.

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

Komplexe Listenelemente

Im Beispiel im vorherigen Abschnitt werden gefälschte Listenelemente verwendet, da die Listenelemente TextView-Objekte sind. Es kann schwieriger sein, gefälschte Artikel bereitzustellen, wenn die Artikel komplexe Layouts haben.

Angenommen, ein Listenelement wird in widget_list_item.xml definiert und besteht aus zwei TextView-Objekten:

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

Wenn Sie gefälschte Listenelemente angeben möchten, können Sie das Layout mehrmals einfügen. Dadurch sind jedoch alle Listenelemente identisch. So stellen Sie eindeutige Listenelemente bereit:

  1. Erstellen Sie eine Reihe von Attributen für die Textwerte:

    <resources>
        <attr name="widgetTitle" format="string" />
        <attr name="widgetContent" format="string" />
    </resources>
    
  2. Verwenden Sie diese Attribute, um den Text festzulegen:

    <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. Erstellen Sie so viele Stile, wie für die Vorschau erforderlich sind. Definieren Sie die Werte in jedem Stil neu:

    <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. Wenden Sie die Stile auf die Platzhalterelemente im Vorschaulayout an:

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