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 die Art, die Häufigkeit und den Zeitpunkt des Updates, um den Akku zu schonen.

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. Beides hat unterschiedliche Rechenkosten und Auswirkungen.

Im Folgenden werden die einzelnen Aktualisierungstypen beschrieben und jeweils entsprechende Code-Snippets bereitgestellt.

  • Vollständige Aktualisierung:Rufen Sie AppWidgetManager.updateAppWidget(int, android.widget.RemoteViews) auf, um das Widget vollständig zu aktualisieren. Dadurch wird das zuvor bereitgestellte RemoteViews durch ein neues RemoteViews ersetzt. Das 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);
    
  • Teilweise Aktualisierung:Durch Aufrufen von AppWidgetManager.partiallyUpdateAppWidget werden Teile des Widgets aktualisiert. Dadurch wird das neue RemoteViews mit dem 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 Sammlungsdaten in Ihrem Widget zu entwerten. 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 der Anwendung aufrufen, solange die Anwendung dieselbe UID wie die entsprechende AppWidgetProvider-Klasse hat.

Festlegen, wie oft ein Widget aktualisiert werden soll

Widgets werden regelmäßig aktualisiert, je nach dem für das Attribut updatePeriodMillis angegebenen Wert. Das Widget kann als Reaktion auf Nutzerinteraktionen und/oder Broadcast-Aktualisierungen aktualisiert werden.

Regelmäßig aktualisieren

Sie können die Häufigkeit der regelmäßigen Aktualisierung steuern, indem Sie in der XML-Datei appwidget-provider einen Wert für AppWidgetProviderInfo.updatePeriodMillis angeben. Jede Aktualisierung löst die Methode AppWidgetProvider.onUpdate() aus. Hier können Sie den Code zum Aktualisieren des Widgets platzieren. Ziehen Sie jedoch die im folgenden Abschnitt beschriebenen Alternativen zu Aktualisierungen von Broadcast-Empfängern in Betracht, 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 Updates in einer Konfiguration anzupassen. Angenommen, ein Börsenticker soll alle 15 Minuten oder nur viermal am Tag aktualisiert werden. Legen Sie in diesem Fall updatePeriodMillis auf 0 fest und verwenden Sie stattdessen WorkManager.

Aktualisierung als Reaktion auf eine Nutzerinteraktion

Im Folgenden finden Sie einige empfohlene Methoden, um das Widget auf Grundlage der Nutzerinteraktion zu aktualisieren:

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

  • Über Remote-Interaktionen wie eine Benachrichtigung oder ein App-Widget:Erstellen Sie ein PendingIntent und aktualisieren Sie das Widget dann über Activity, Broadcast oder Service. Du kannst deine eigene Priorität festlegen. Wenn du beispielsweise ein Broadcast für PendingIntent auswählst, kannst du eine Übertragung im Vordergrund auswählen, um der Priorität BroadcastReceiver zuzuweisen.

Aktualisierung als Reaktion auf eine Nachricht an alle

Ein Beispiel für ein Broadcast-Ereignis, bei dem ein Widget aktualisiert werden muss, ist, wenn der Nutzer ein Foto aufnimmt. In diesem Fall möchten Sie das Widget aktualisieren, wenn ein neues Foto erkannt wird.

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

Sie können auch einen BroadcastReceiver für die Übertragung registrieren und beispielsweise auf ACTION_LOCALE_CHANGED warten. Da dies jedoch Geräteressourcen beansprucht, sollten Sie dies mit Vorsicht verwenden und nur den spezifischen Broadcast anhören. Mit der Einführung von Übertragungseinschränkungen in Android 7.0 (API-Ebene 24) und Android 8.0 (API-Ebene 26) können Apps mit bestimmten Ausnahmen keine impliziten Broadcasts in ihren Manifesten registrieren.

Überlegungen beim Aktualisieren eines Widgets über einen BroadcastReceiver

Wenn das Widget von einer BroadcastReceiver, einschließlich AppWidgetProvider, aktualisiert wird, müssen Sie die folgenden Hinweise zur Dauer und Priorität eines Widget-Updates beachten.

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 reagieren eingestuft werden und den ANR-Fehler Application Not Responding (ANR-Fehler) auslösen. Wenn die Aktualisierung des Widgets länger dauert, können Sie Folgendes tun:

  • Planen Sie eine Aufgabe mit WorkManager.

  • Geben Sie dem Empfänger mit der Methode goAsync mehr Zeit. Dadurch können Empfänger 30 Sekunden lang ausführen.

Weitere Informationen finden Sie unter Sicherheitsaspekte und Best Practices.

Priorität des Updates

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

Füge beispielsweise dem Intent das Flag Intent.FLAG_RECEIVER_FOREGROUND 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 : Widget-Vorschau ohne Listenelemente.

In diesem Abschnitt wird der empfohlene Ansatz zum Anzeigen mehrerer Elemente in einer Widget-Vorschau für ein Widget mit einer Sammlungsansicht erläutert, d. h. für ein Widget, das ListView, GridView oder StackView verwendet.

Wenn Ihr Widget eine dieser Ansichten verwendet, beeinträchtigt das Erstellen einer skalierbaren Vorschau durch direktes Bereitstellen des tatsächlichen Widget-Layouts die Nutzererfahrung, wenn in der Widgetvorschau 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 nur für die Vorschau beizubehalten. Diese separate Layoutdatei enthält das eigentliche Widget-Layout und eine Platzhalter-Sammlungsansicht mit fiktiven Elementen. Sie können beispielsweise eine 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>

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

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

Komplexe Listenelemente

Das Beispiel im vorherigen Abschnitt enthält gefälschte Listenelemente, da die Listenelemente TextView-Objekte sind. Die Bereitstellung gefälschter Elemente kann komplexer sein, wenn es sich um komplexe Layouts handelt.

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 verwenden. Dies führt jedoch dazu, dass alle Listenelemente identisch sind. So geben Sie eindeutige Listenelemente an:

  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>