En esta página, se explican las prácticas recomendadas para crear un widget más avanzado que brinde una mejor experiencia del usuario.
Optimizaciones para actualizar el contenido de los widgets
La actualización del contenido de los widgets puede ser costosa en términos de procesamiento. Para ahorrar batería, optimiza el tipo de actualización, la frecuencia y el tiempo.
Tipos de actualizaciones de widgets
Existen tres maneras de actualizar un widget: una actualización completa, una actualización parcial y, en el caso de un widget de colección, una actualización de datos. Cada uno tiene diferentes costos computacionales y ramificaciones.
A continuación, se describe cada tipo de actualización y se proporcionan fragmentos de código para cada uno.
Actualización completa: Llama a
AppWidgetManager.updateAppWidget(int, android.widget.RemoteViews)
para actualizar el widget por completo. Esto reemplaza elRemoteViews
proporcionado anteriormente por unRemoteViews
nuevo. Esta es la actualización más costosa en términos de procesamiento.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);
Actualización parcial: Llama a
AppWidgetManager.partiallyUpdateAppWidget
para actualizar partes del widget. Esto combina elRemoteViews
nuevo con elRemoteViews
proporcionado anteriormente. Este método se ignora si un widget no recibe al menos una actualización completa a través deupdateAppWidget(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);
Actualización de datos de la colección: Llama a
AppWidgetManager.notifyAppWidgetViewDataChanged
para invalidar los datos de una vista de colección en tu widget. Esto activaRemoteViewsFactory.onDataSetChanged
. Mientras tanto, los datos anteriores se muestran en el widget. Con este método, puedes realizar tareas costosas de forma síncrona de forma segura.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);
Puedes llamar a estos métodos desde cualquier lugar de tu app, siempre que esta tenga el mismo UID que la clase AppWidgetProvider
correspondiente.
Cómo determinar la frecuencia con la que se debe actualizar un widget
Los widgets se actualizan periódicamente según el valor proporcionado para el atributo updatePeriodMillis
. El widget puede actualizarse en respuesta a la interacción del usuario, transmitir actualizaciones o ambas.
Actualiza periódicamente
Para controlar la frecuencia de la actualización periódica, especifica un valor para AppWidgetProviderInfo.updatePeriodMillis
en el archivo en formato XML appwidget-provider
. Cada actualización activa el método AppWidgetProvider.onUpdate()
, que es donde puedes colocar el código para actualizar el widget. Sin embargo, ten en cuenta las alternativas para las actualizaciones del receptor de emisión que se describen en la siguiente sección si tu widget necesita cargar datos de forma asíncrona o si tarda más de 10 segundos en actualizarse, porque después de 10 segundos, el sistema considera que un BroadcastReceiver
no responde.
updatePeriodMillis
no admite valores inferiores a 30 minutos. Sin embargo, si quieres inhabilitar las actualizaciones periódicas, puedes especificar 0.
Puedes permitir que los usuarios ajusten la frecuencia de las actualizaciones en una configuración. Por ejemplo, es posible que quieran que un código bursátil se actualice cada 15 minutos o solo cuatro veces al día. En este caso, establece updatePeriodMillis
en 0 y usa WorkManager
en su lugar.
Actualización en respuesta a una interacción del usuario
Estas son algunas formas recomendadas de actualizar el widget según la interacción del usuario:
Desde una actividad de la app: Llama directamente a
AppWidgetManager.updateAppWidget
en respuesta a una interacción del usuario, como cuando la presiona.Desde interacciones remotas, como una notificación o un widget de app: Construye un
PendingIntent
y, luego, actualiza el widget desde elActivity
,Broadcast
oService
invocado. Puedes elegir tu propia prioridad. Por ejemplo, si seleccionas unBroadcast
para elPendingIntent
, puedes elegir una transmisión en primer plano para darle prioridad aBroadcastReceiver
.
Actualizar en respuesta a un evento de transmisión
Un ejemplo de un evento de transmisión que requiere que se actualice un widget es cuando el usuario toma una foto. En este caso, quieres actualizar el widget cuando se detecta una foto nueva.
Puedes programar un trabajo con JobScheduler
y especificar una transmisión como activador mediante el método JobInfo.Builder.addTriggerContentUri
.
También puedes registrar un BroadcastReceiver
para la transmisión, por ejemplo, escuchar ACTION_LOCALE_CHANGED
.
Sin embargo, como esta función consume recursos del dispositivo, úsala con cuidado y escucha solo la transmisión específica. Con la introducción de las limitaciones de emisión en Android 7.0 (nivel de API 24) y Android 8.0 (nivel de API 26), las apps no pueden registrar emisiones implícitas en sus manifiestos, con ciertas excepciones.
Consideraciones para actualizar un widget desde un BroadcastReceiver
Si el widget se actualiza desde un BroadcastReceiver
, incluido AppWidgetProvider
, ten en cuenta las siguientes consideraciones con respecto a la duración y la prioridad de una actualización de widget.
Duración de la actualización
Por lo general, el sistema permite que los receptores de emisión, que suelen ejecutarse en el subproceso principal de la app, se ejecuten durante hasta 10 segundos antes de considerar que no responden y activar un error de Aplicación no responde (ANR). Si lleva más tiempo actualizar el widget, considera las siguientes alternativas:
Programa una tarea con
WorkManager
.Dale más tiempo al receptor con el método
goAsync
. Esto permite que los receptores se ejecuten durante 30 segundos.
Consulta las consideraciones y prácticas recomendadas de seguridad para obtener más información.
Prioridad de la actualización
De forma predeterminada, las transmisiones, incluidas las que se realizan con AppWidgetProvider.onUpdate
, se ejecutan como procesos en segundo plano. Esto significa que los recursos del sistema sobrecargados pueden causar una demora en la invocación del receptor de emisión. Para priorizar la transmisión, conviértela en un proceso en primer plano.
Por ejemplo, agrega la marca Intent.FLAG_RECEIVER_FOREGROUND
a la Intent
que se pasa a PendingIntent.getBroadcast
cuando el usuario presiona una parte determinada del widget.
Crea vistas previas precisas que incluyan elementos dinámicos
En esta sección, se explica el enfoque recomendado para mostrar varios elementos en una vista previa de widget para un widget con una vista de colección, es decir, un widget que usa ListView
, GridView
o StackView
.
Si tu widget usa una de estas vistas, crear una vista previa escalable proporcionando directamente el diseño del widget real degrada la experiencia cuando la vista previa del widget no muestra ningún elemento. Esto ocurre porque los datos de la vista de colección se establecen de forma dinámica durante el tiempo de ejecución y se ven similares a la imagen que se muestra en la Figura 1.
Para que las vistas previas de los widgets con vistas de colecciones se muestren correctamente en el selector de widgets, te recomendamos que mantengas un archivo de diseño independiente designado solo para la vista previa. Este archivo de diseño independiente incluye el diseño real del widget y una vista de colección de marcador de posición con elementos falsos. Por ejemplo, puedes imitar un ListView
proporcionando un marcador de posición LinearLayout
con varios elementos de lista falsos.
Para ilustrar un ejemplo de un ListView
, comienza con un archivo de diseño independiente:
// 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>
Especifica el archivo de diseño de vista previa cuando proporciones el atributo previewLayout
de los metadatos AppWidgetProviderInfo
. Aún especificas el diseño real del widget para el atributo initialLayout
y usas el diseño real del widget cuando construyes un RemoteViews
en el tiempo de ejecución.
<appwidget-provider
previewLayout="@layout/widget_previe"
initialLayout="@layout/widget_view" />
Elementos de lista complejos
El ejemplo de la sección anterior proporciona elementos de lista falsos, ya que los elementos de lista son objetos TextView
. Puede ser más complejo proporcionar elementos falsos si los elementos son diseños complejos.
Considera un elemento de lista que se define en widget_list_item.xml
y consta de
dos objetos 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>
Para proporcionar elementos de lista falsos, puedes incluir el diseño varias veces, pero eso hace que cada elemento sea idéntico. Para proporcionar elementos de lista únicos, sigue estos pasos:
Crea un conjunto de atributos para los valores de texto:
<resources> <attr name="widgetTitle" format="string" /> <attr name="widgetContent" format="string" /> </resources>
Usa estos atributos para establecer el texto:
<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>
Crea tantos estilos como sea necesario para la vista previa. Vuelve a definir los valores en cada estilo:
<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>
Aplica los estilos a los elementos falsos en el diseño de vista previa:
<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>