Erweitertes Widget erstellen

Auf dieser Seite werden Best Practices für die Erstellung eines erweiterten Widgets beschrieben, um die Nutzerfreundlichkeit zu verbessern.

Optimierungen für die Aktualisierung von Widget-Inhalten

Das Aktualisieren von Widget-Inhalten kann rechenintensiv sein. Optimieren Sie den Aktualisierungstyp, die Häufigkeit und das Timing, um den Akkuverbrauch zu senken.

Arten von Widget-Updates

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

Im Folgenden werden die einzelnen Updatetypen 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 der zuvor angegebene Messwert RemoteViews durch einen neuen Messwert 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);
    
  • 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 mindestens einmal vollständig über updateAppWidget(int[], RemoteViews) aktualisiert wurde.

    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);
    
  • Daten einer Sammlung aktualisieren:Rufen Sie AppWidgetManager.notifyAppWidgetViewDataChanged auf, um die Daten einer Sammlungsdatenansicht 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 synchron und sicher 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, solange die App dieselbe UID wie die entsprechende AppWidgetProvider-Klasse hat.

Festlegen, wie oft ein Widget aktualisiert werden soll

Widgets werden regelmäßig aktualisiert, je nachdem, welcher Wert für das Attribut updatePeriodMillis angegeben ist. Das Widget kann als Reaktion auf Nutzerinteraktionen, als Übertragung von 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 platzieren, um das Widget zu aktualisieren. Berücksichtigen Sie jedoch die Alternativen für Aktualisierungen von Broadcastreceivern im folgenden Abschnitt, wenn Ihr Widget Daten asynchron laden muss oder die Aktualisierung mehr als 10 Sekunden dauert. Nach 10 Sekunden betrachtet das System eine BroadcastReceiver als nicht reagierend.

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. So kann ein Aktienticker beispielsweise 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

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

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

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

Aktualisierung als Reaktion auf ein Übertragungsereignis

Ein Beispiel für ein gesendetes 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 einen Stream als Trigger angeben.

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

Hinweise zum Aktualisieren eines Widgets über einen BroadcastReceiver

Wenn das Widget über eine BroadcastReceiver-Version aktualisiert wird, einschließlich AppWidgetProvider, beachten Sie die folgenden Hinweise zur Dauer und Priorität eines Widget-Updates.

Dauer der Aktualisierung

Normalerweise lässt das System Broadcast-Empfänger, die normalerweise im Haupt-Thread der App ausgeführt werden, bis zu 10 Sekunden lang laufen, bevor sie als nicht reagierend eingestuft und ein ANR-Fehler (Application Not Responding, Anwendung reagiert nicht) ausgelöst wird. Wenn das Aktualisieren des Widgets länger dauert, haben Sie folgende Möglichkeiten:

  • Aufgaben mit WorkManager planen

  • Mit der Methode goAsync kannst du dem Empfänger mehr Zeit geben. Dadurch können Empfänger 30 Sekunden lang ausgeführt werden.

Weitere Informationen finden Sie unter Sicherheitsaspekte und Best Practices.

Priorität des Updates

Standardmäßig werden Übertragungen – auch solche, die mit AppWidgetProvider.onUpdate erstellt wurden – als Hintergrundprozesse ausgeführt. Das bedeutet, dass überlastete Systemressourcen zu einer Verzögerung bei der Aufrufung des Broadcastempfängers 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 dem Intent hinzu, das an die 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 für die Anzeige mehrerer Elemente in einer Widget-Vorschau für ein Widget mit einer Sammlungsansicht beschrieben, also für ein Widget, das ListView, GridView oder StackView verwendet.

Wenn für Ihr Widget eine dieser Ansichten verwendet wird, ist die Nutzerfreundlichkeit eingeschränkt, wenn Sie eine skalierbare Vorschau erstellen, indem Sie direkt das tatsächliche Widget-Layout angeben, und in der Widget-Vorschau keine Elemente angezeigt werden. Das liegt daran, dass Daten in Sammlungsansichten während der Laufzeit dynamisch festgelegt werden. Das Bild in Abbildung 1 zeigt, wie das funktioniert.

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 Platzhaltersammlungsansicht 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 eine 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 previewLayout-Attribut der AppWidgetProviderInfo-Metadaten angeben. Sie geben weiterhin das tatsächliche Widget-Layout für das initialLayout-Attribut an und verwenden es beim Erstellen eines RemoteViews zur Laufzeit.

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

Komplexe Listenelemente

Im Beispiel im vorherigen Abschnitt werden gefälschte Listenelemente verwendet, da es sich bei den Listenelementen um TextView-Objekte handelt. Es kann schwieriger sein, gefälschte Artikel bereitzustellen, wenn es sich um komplexe Layouts handelt.

Angenommen, ein Listenelement ist 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 einfügen möchten, können Sie das Layout mehrmals einfügen. Dadurch sind jedoch alle Listenelemente identisch. 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. 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 fiktiven 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>