Erweitertes Widget erstellen

Auf dieser Seite werden empfohlene Vorgehensweisen zum Erstellen eines erweiterten Widgets für eine bessere Nutzererfahrung erläutert.

Optimierungen für die Aktualisierung von Widget-Inhalten

Das Aktualisieren von Widget-Inhalten kann rechenintensiv sein. Optimieren Sie den Updatetyp, die Häufigkeit und den Zeitpunkt der Aktualisierung, um den Akku zu schonen.

Arten von Widget-Updates

Es gibt drei Möglichkeiten, ein Widget zu aktualisieren: eine vollständige Aktualisierung, eine teilweise Aktualisierung und, bei einem Sammlungs-Widget, eine Datenaktualisierung. Jedes hat unterschiedliche Rechenkosten und Auswirkungen.

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

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

    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);
    
  • Teilweise Aktualisierung:Rufen Sie AppWidgetManager.partiallyUpdateAppWidget auf, um Teile des Widgets zu aktualisieren. Dadurch wird die neue RemoteViews mit der zuvor bereitgestellten 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 der Sammlungsdaten:Rufen Sie AppWidgetManager.notifyAppWidgetViewDataChanged auf, um die Daten einer Sammlungsansicht in Ihrem Widget ungültig zu machen. Dadurch wird RemoteViewsFactory.onDataSetChanged ausgelöst. In der Zwischenzeit werden die alten Daten im Widget angezeigt. Mit dieser Methode können Sie teure 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 Anwendung aufrufen, solange die Anwendung dieselbe UID hat wie die entsprechende AppWidgetProvider-Klasse.

Bestimmen, wie oft ein Widget aktualisiert werden soll

Widgets werden regelmäßig aktualisiert, je nachdem, welchen Wert das Attribut updatePeriodMillis angegeben hat. 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 festlegen, indem Sie in der XML-Datei appwidget-provider einen Wert für AppWidgetProviderInfo.updatePeriodMillis angeben. Bei jeder Aktualisierung wird die Methode AppWidgetProvider.onUpdate() ausgelöst. Dort können Sie den Code zum Aktualisieren des Widgets platzieren. Beachten Sie jedoch die in einem folgenden Abschnitt beschriebenen Alternativen für Broadcast-Empfänger-Updates, wenn Ihr Widget Daten asynchron laden muss oder die Aktualisierung länger als 10 Sekunden dauert, da das System eine BroadcastReceiver nach 10 Sekunden als nicht reagiert erachtet.

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

Sie können Nutzern erlauben, die Häufigkeit von Aktualisierungen in einer Konfiguration anzupassen. Angenommen, ein Börsenticker soll alle 15 Minuten oder nur viermal am Tag aktualisiert werden. 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 der Nutzerinteraktion zu aktualisieren:

  • Über eine Aktivität der App:Rufe AppWidgetManager.updateAppWidget als Reaktion auf eine Nutzerinteraktion, z. B. das Tippen des Nutzers, direkt auf.

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

Aktualisierung als Reaktion auf eine Übertragung

Ein Beispiel für ein Übertragungsereignis, bei dem ein Widget aktualisiert werden muss, ist das Aufnehmen eines Fotos durch den Nutzer. In diesem Fall möchten Sie das Widget aktualisieren, sobald ein neues Foto erkannt wird.

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

Sie können auch eine BroadcastReceiver für die Übertragung registrieren, z. B. auf ACTION_LOCALE_CHANGED warten. Da dies jedoch Geräteressourcen beansprucht, sollten Sie diese Option mit Bedacht verwenden und nur die jeweilige Übertragung abhören. Seit der Einführung von Übertragungsbeschränkungen in Android 7.0 (API-Level 24) und Android 8.0 (API-Level 26) können Apps keine impliziten Übertragungen in ihren Manifesten registrieren. Es gibt allerdings einige Ausnahmen.

Überlegungen zur Aktualisierung eines Widgets von einem BroadcastReceiver

Wenn das Widget über eine BroadcastReceiver, einschließlich AppWidgetProvider, aktualisiert wird, beachten Sie die folgenden Überlegungen bezüglich der Dauer und Priorität eines Widget-Updates.

Dauer der Aktualisierung

In der Regel lässt das System Übertragungsempfänger, die normalerweise im Hauptthread der App ausgeführt werden, bis zu 10 Sekunden lang laufen, bevor sie als nicht reagierend eingestuft werden und einen ANR-Fehler (Application Not Responding) auslösen. Wenn die Aktualisierung des Widgets länger dauert, hast du folgende Alternativen:

  • Planen Sie eine Aufgabe mit WorkManager.

  • Geben Sie dem Empfänger mit der Methode goAsync mehr Zeit. Dadurch wird die Ausführung durch Empfänger 30 Sekunden lang ermöglicht.

Weitere Informationen finden Sie unter Sicherheitsaspekte und Best Practices.

Priorität des Updates

Standardmäßig werden Broadcasts – einschließlich solcher mit AppWidgetProvider.onUpdate – als Hintergrundprozesse ausgeführt. Das bedeutet, dass überlastete Systemressourcen zu einer Verzögerung beim Aufruf des Übertragungsempfängers führen können. Wenn du die Übertragung priorisieren möchtest, solltest du sie als Vordergrundprozess festlegen.

Du kannst beispielsweise das Flag Intent.FLAG_RECEIVER_FOREGROUND in die Intent einfügen, die an PendingIntent.getBroadcast übergeben wird, wenn der Nutzer auf einen bestimmten Teil des Widgets tippt.

Genaue Vorschauen erstellen, die dynamische Elemente enthalten

Abbildung 1 : 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 erläutert, also einem Widget, das ListView, GridView oder StackView verwendet.

Wenn dein Widget eine dieser Ansichten verwendet, beeinträchtigt die Erstellung einer skalierbaren Vorschau durch die direkte Bereitstellung des Widget-Layouts die Nutzung, wenn in der Widget-Vorschau keine Elemente angezeigt werden. Das liegt daran, dass die Daten der Sammlungsansicht zur Laufzeit dynamisch festgelegt werden und dem Bild in Abbildung 1 ähneln.

Damit Vorschauen von Widgets mit Sammlungsansichten korrekt in der Widget-Auswahl angezeigt werden, empfehlen wir, eine separate Layoutdatei zu erstellen, die nur für die Vorschau vorgesehen ist. Diese separate Layoutdatei enthält das eigentliche Widget-Layout und eine Platzhaltersammlungsansicht mit gefälschten Elementen. Sie können z. B. ein ListView nachahmen, indem Sie einen Platzhalter-LinearLayout mit mehreren gefälschten Listenelementen angeben.

Um ein Beispiel für ein ListView zu veranschaulichen, beginnen Sie mit einer separaten Layoutdatei:

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

Gib die Vorschau-Layoutdatei an, wenn du das Attribut previewLayout der AppWidgetProviderInfo-Metadaten angibst. Für das Attribut initialLayout geben Sie weiterhin das tatsächliche Widget-Layout an und verwenden das tatsächliche Widget-Layout, wenn Sie eine RemoteViews zur Laufzeit erstellen.

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

Komplexe Listenelemente

Das Beispiel im vorherigen Abschnitt enthält falsche Listenelemente, da die Listenelemente TextView-Objekte sind. Bei komplexen Layouts kann es komplexer sein, gefälschte Elemente bereitzustellen.

Hier ein Listenelement, das in widget_list_item.xml definiert ist und aus zwei TextView-Objekten besteht:

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

Um gefälschte Listenelemente bereitzustellen, können Sie das Layout mehrmals einfügen. Dies führt jedoch dazu, dass jedes Listenelement identisch ist. 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 gefälschten Elemente 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>