Cette page présente les pratiques recommandées pour créer un widget plus avancé afin d'améliorer l'expérience utilisateur.
Optimisations pour la mise à jour du contenu des widgets
La mise à jour du contenu d'un widget peut nécessiter des ressources de calcul importantes. Pour économiser la batterie, optimisez le type, la fréquence et le calendrier des mises à jour.
Types de mises à jour de widgets
Il existe trois façons de mettre à jour un widget: une mise à jour complète, une mise à jour partielle et, dans le cas d'un widget de collection, une actualisation des données. Chacun d'entre eux a des coûts de calcul et des ramifications différents.
Vous trouverez ci-dessous la description de chaque type de mise à jour et des extraits de code pour chacune d'elles.
Mise à jour complète:appelez
AppWidgetManager.updateAppWidget(int, android.widget.RemoteViews)
pour mettre à jour entièrement le widget. Cela remplace leRemoteViews
fourni précédemment par un nouveauRemoteViews
. Il s'agit de la mise à jour la plus coûteuse en calcul.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);
Mise à jour partielle:appelez
AppWidgetManager.partiallyUpdateAppWidget
pour mettre à jour certaines parties du widget. Le nouveauRemoteViews
est fusionné avec leRemoteViews
fourni précédemment. Cette méthode est ignorée si un widget ne reçoit pas au moins une mise à jour complète viaupdateAppWidget(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);
Actualisation des données de collection:appelez
AppWidgetManager.notifyAppWidgetViewDataChanged
pour invalider les données d'une vue de collection dans votre widget. Cela déclencheRemoteViewsFactory.onDataSetChanged
. En attendant, les anciennes données sont affichées dans le widget. Cette méthode vous permet d'effectuer des tâches coûteuses de manière synchrone et sécurisée.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);
Vous pouvez appeler ces méthodes depuis n'importe où dans votre application, à condition qu'elle ait le même UID que la classe AppWidgetProvider
correspondante.
Déterminer la fréquence de mise à jour d'un widget
Les widgets sont mis à jour régulièrement en fonction de la valeur fournie pour l'attribut updatePeriodMillis
. Le widget peut se mettre à jour en réponse à une interaction de l'utilisateur, à des mises à jour de diffusion, ou aux deux.
Mettre à jour régulièrement
Vous pouvez contrôler la fréquence de la mise à jour périodique en spécifiant une valeur pour AppWidgetProviderInfo.updatePeriodMillis
dans le fichier XML de appwidget-provider
. Chaque mise à jour déclenche la méthode AppWidgetProvider.onUpdate()
, où vous pouvez placer le code pour mettre à jour le widget. Toutefois, reportez-vous aux alternatives pour les mises à jour du broadcast receiver décrites dans la section suivante si votre widget doit charger les données de manière asynchrone ou si la mise à jour prend plus de 10 secondes. En effet, au bout de 10 secondes, le système considère qu'un BroadcastReceiver
ne répond pas.
updatePeriodMillis
n'accepte pas les valeurs inférieures à 30 minutes. Toutefois, si vous souhaitez désactiver les mises à jour périodiques, vous pouvez spécifier 0.
Vous pouvez autoriser les utilisateurs à ajuster la fréquence des mises à jour dans une configuration. Par exemple, ils peuvent vouloir qu'un symbole boursier se met à jour toutes les 15 minutes ou seulement quatre fois par jour. Dans ce cas, définissez updatePeriodMillis
sur 0 et utilisez WorkManager
à la place.
Mise à jour en réponse à une interaction utilisateur
Voici quelques recommandations pour mettre à jour le widget en fonction de l'interaction de l'utilisateur:
À partir d'une activité de l'application:appelez directement
AppWidgetManager.updateAppWidget
en réponse à une interaction de l'utilisateur, par exemple lorsqu'il appuie dessus.À partir d'interactions à distance, telles qu'une notification ou un widget d'application:construisez un
PendingIntent
, puis mettez à jour le widget à partir deActivity
,Broadcast
ouService
appelé. Vous pouvez définir votre propre priorité. Par exemple, si vous sélectionnez unBroadcast
pourPendingIntent
, vous pouvez choisir une diffusion au premier plan pour donner la priorité àBroadcastReceiver
.
Mettre à jour en réponse à un événement de diffusion
Voici un exemple d'événement de diffusion nécessitant la mise à jour d'un widget : lorsque l'utilisateur prend une photo. Dans ce cas, vous souhaitez mettre à jour le widget lorsqu'une nouvelle photo est détectée.
Vous pouvez planifier une tâche avec JobScheduler
et spécifier une diffusion comme déclencheur à l'aide de la méthode JobInfo.Builder.addTriggerContentUri
.
Vous pouvez également enregistrer un BroadcastReceiver
pour la diffusion, par exemple en écoutant ACTION_LOCALE_CHANGED
.
Toutefois, comme cela consomme des ressources de l'appareil, utilisez-le avec précaution et n'écoutez que la diffusion spécifique. Avec l'introduction de limites de diffusion dans Android 7.0 (niveau d'API 24) et Android 8.0 (niveau d'API 26), les applications ne peuvent plus enregistrer de diffusions implicites dans leurs fichiers manifestes, à quelques exceptions près.
Éléments à prendre en compte lors de la mise à jour d'un widget à partir d'un BroadcastReceiver
Si le widget est mis à jour à partir d'un BroadcastReceiver
, y compris AppWidgetProvider
, tenez compte des considérations suivantes concernant la durée et la priorité de cette mise à jour.
Durée de la mise à jour
En règle générale, le système permet aux broadcast receivers, qui s'exécutent généralement dans le thread principal de l'application, de s'exécuter pendant 10 secondes maximum avant de considérer qu'ils ne répondent pas et de déclencher une erreur L'application ne répond pas (ANR). Si la mise à jour du widget prend plus de temps, envisagez les solutions suivantes:
Planifiez une tâche à l'aide de
WorkManager
.Accordez plus de temps au récepteur avec la méthode
goAsync
. Cela permet aux destinataires de s'exécuter pendant 30 secondes.
Pour plus d'informations, consultez la page Considérations de sécurité et bonnes pratiques.
Priorité de la mise à jour
Par défaut, les annonces, y compris celles effectuées à l'aide de AppWidgetProvider.onUpdate
, s'exécutent en tant que processus en arrière-plan. Cela signifie que des ressources système surchargées peuvent entraîner un retard d'appel du broadcast receiver. Pour prioriser la diffusion, placez-la au premier plan.
Par exemple, ajoutez l'indicateur Intent.FLAG_RECEIVER_FOREGROUND
au Intent
transmis à PendingIntent.getBroadcast
lorsque l'utilisateur appuie sur une certaine partie du widget.
Créer des aperçus précis incluant des éléments dynamiques
Cette section explique l'approche recommandée pour afficher plusieurs éléments dans un aperçu de widget avec une vue de collection, c'est-à-dire un widget qui utilise un ListView
, un GridView
ou un StackView
.
Si votre widget utilise l'une de ces vues, la création d'un aperçu évolutif en fournissant directement la mise en page réelle du widget nuit à l'expérience lorsque l'aperçu du widget n'affiche aucun élément. En effet, les données de la vue de collection sont définies de manière dynamique au moment de l'exécution et ressemblent à l'image illustrée dans la figure 1.
Pour que les aperçus des widgets avec des vues de collection s'affichent correctement dans le sélecteur de widgets, nous vous recommandons de gérer un fichier de mise en page distinct réservé à l'aperçu. Ce fichier de mise en page distinct inclut la mise en page réelle du widget et une vue de collection d'espaces réservés avec des éléments factices. Par exemple, vous pouvez imiter un ListView
en fournissant un espace réservé LinearLayout
avec plusieurs faux éléments de liste.
Pour illustrer un exemple de ListView
, commencez avec un fichier de mise en page distinct:
// 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>
Spécifiez le fichier de mise en page d'aperçu lorsque vous fournissez l'attribut previewLayout
des métadonnées AppWidgetProviderInfo
. Vous devez toujours spécifier la mise en page réelle du widget pour l'attribut initialLayout
et utiliser cette mise en page lors de la construction d'un RemoteViews
au moment de l'exécution.
<appwidget-provider
previewLayout="@layout/widget_previe"
initialLayout="@layout/widget_view" />
Éléments de liste complexes
L'exemple de la section précédente fournit de faux éléments de liste, car ils sont des objets TextView
. Il peut être plus complexe de fournir des éléments factices si les éléments sont des mises en page complexes.
Prenons l'exemple d'un élément de liste défini dans widget_list_item.xml
et composé de deux objets 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>
Pour fournir de faux éléments de liste, vous pouvez inclure la mise en page plusieurs fois, mais chaque élément de liste sera alors identique. Pour fournir des éléments de liste uniques, procédez comme suit:
Créez un ensemble d'attributs pour les valeurs textuelles:
<resources> <attr name="widgetTitle" format="string" /> <attr name="widgetContent" format="string" /> </resources>
Utilisez les attributs suivants pour définir le texte:
<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>
Créez autant de styles que nécessaire pour l'aperçu. Redéfinissez les valeurs dans chaque style:
<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>
Appliquez les styles aux éléments fictifs dans la mise en page de l'aperçu:
<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>