Nesta página, explicamos as práticas recomendadas para criar um widget mais avançado e melhorar a experiência do usuário.
Otimizações para atualizar o conteúdo do widget
Atualizar o conteúdo do widget pode ser caro em termos computacionais. Para economizar a bateria, otimize o tipo, a frequência e o momento da atualização.
Tipos de atualizações de widgets
Há três maneiras de atualizar um widget: uma atualização completa, uma parcial e, no caso de um widget de coleção, uma atualização de dados. Cada um tem custos computacionais e ramificações diferentes.
A seguir, descrevemos cada tipo de atualização e fornecemos snippets de código para cada um deles.
Atualização completa: chame
AppWidgetManager.updateAppWidget(int, android.widget.RemoteViews)
para atualizar completamente o widget. Isso substitui oRemoteViews
fornecido anteriormente por um novoRemoteViews
. Essa é a atualização mais cara em termos de computação.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);
Atualização parcial: chame
AppWidgetManager.partiallyUpdateAppWidget
para atualizar partes do widget. Isso mescla o novoRemoteViews
com oRemoteViews
fornecido anteriormente. Esse método é ignorado se um widget não receber pelo menos uma atualização completa peloupdateAppWidget(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);
Atualização de dados da coleção: chame
AppWidgetManager.notifyAppWidgetViewDataChanged
para invalidar os dados de uma visualização de coleção no widget. Isso acionaRemoteViewsFactory.onDataSetChanged
. Enquanto isso, os dados antigos são exibidos no widget. É possível realizar tarefas caras de forma síncrona com segurança com esse método.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);
É possível chamar esses métodos em qualquer lugar do app, desde que ele tenha o
mesmo UID da classe
AppWidgetProvider
correspondente.
Determinar a frequência de atualização de um widget
Os widgets são atualizados periodicamente, dependendo do valor fornecido para o
atributo
updatePeriodMillis
. O widget pode ser atualizado em resposta à interação do usuário, às atualizações
do app ou a ambos.
Atualize periodicamente
É possível controlar a frequência da atualização periódica especificando um valor para
AppWidgetProviderInfo.updatePeriodMillis
no XML appwidget-provider
. Cada
atualização aciona o método AppWidgetProvider.onUpdate()
, que é onde você
pode colocar o código para atualizar o widget. No entanto, considere as alternativas para
atualizações de broadcast receiver descritas na
próxima seção se o widget precisar carregar dados de forma assíncrona ou levar mais
de 10 segundos para atualizar. Isso porque, após 10 segundos, o sistema considera que um
BroadcastReceiver
não responde.
O updatePeriodMillis
não aceita valores menores que 30 minutos. No entanto, se
você quiser desativar as atualizações periódicas, especifique 0.
Você pode permitir que os usuários ajustem a frequência das atualizações em uma configuração. Por
exemplo, eles podem querer que um mostrador de ações seja atualizado a cada 15 minutos ou apenas quatro
vezes por dia. Nesse caso, defina updatePeriodMillis
como 0 e use
WorkManager
.
Atualizar em resposta a uma interação do usuário
Aqui estão algumas maneiras recomendadas de atualizar o widget com base na interação do usuário:
De uma atividade do app:chame
AppWidgetManager.updateAppWidget
diretamente em resposta a uma interação do usuário, como um toque.De interações remotas, como uma notificação ou um widget de app:crie um
PendingIntent
e atualize o widget usando oActivity
,Broadcast
ouService
invocado. Você pode escolher sua própria prioridade. Por exemplo, se você selecionar umBroadcast
para oPendingIntent
, poderá escolher uma transmissão em primeiro plano para dar prioridade aoBroadcastReceiver
.
Atualizar em resposta a um evento de transmissão
Um exemplo de evento de transmissão que exige a atualização de um widget é quando o usuário tira uma foto. Nesse caso, você quer atualizar o widget quando uma nova foto for detectada.
É possível programar um job com JobScheduler
e especificar uma transmissão como o
gatilho usando o método
JobInfo.Builder.addTriggerContentUri
.
Você também pode registrar um BroadcastReceiver
para a transmissão, por exemplo,
detectando
ACTION_LOCALE_CHANGED
.
No entanto, como isso consome recursos do dispositivo, use com cuidado e ouça
apenas a transmissão específica. Com a introdução das limitações
de transmissão no Android
7.0 (nível 24 da API) e no Android 8.0 (nível 26 da API), os apps não podem registrar transmissões
implícitas nos manifestos, com algumas
exceções.
Considerações ao atualizar um widget de um BroadcastReceiver
Se o widget for atualizado de um BroadcastReceiver
, incluindo
AppWidgetProvider
, considere as seguintes considerações sobre a
duração e a prioridade de uma atualização de widget.
Duração da atualização
Por padrão, o sistema permite que os broadcast receivers, que geralmente são executados na linha de execução principal do app, sejam executados por até 10 segundos antes de considerá-los não responsivos e acionando um erro O app não está respondendo (ANR, na sigla em inglês). Se demorar mais para atualizar o widget, considere as seguintes alternativas:
Programe uma tarefa usando
WorkManager
.Dê mais tempo ao receptor com o método
goAsync
. Isso permite que os receptores sejam executados por 30 segundos.
Consulte Considerações e práticas recomendadas de segurança para mais informações.
Prioridade da atualização
Por padrão, as transmissões, incluindo as feitas usando
AppWidgetProvider.onUpdate
, são executadas como processos em segundo plano. Isso significa
que os recursos do sistema sobrecarregados podem causar um atraso na invocação do broadcast
receiver. Para priorizar a transmissão, torne-a um processo em primeiro plano.
Por exemplo, adicione a sinalização
Intent.FLAG_RECEIVER_FOREGROUND
ao Intent
transmitido ao PendingIntent.getBroadcast
quando o usuário
tocar em uma determinada parte do widget.
Criar visualizações precisas que incluem itens dinâmicos
Esta seção explica a abordagem recomendada para mostrar vários itens em
uma visualização de widget com uma visualização
de coleção, ou seja, um widget que usa um
ListView
, GridView
ou StackView
.
Se o widget usar uma dessas visualizações, criar uma visualização escalonável ao fornecer diretamente o layout do widget real vai prejudicar a experiência quando a visualização do widget não mostrar itens. Isso ocorre porque os dados da visualização da coleção são definidos dinamicamente no momento da execução e são semelhantes à imagem mostrada na Figura 1.
Para que as visualizações de widgets com visualizações de coleção sejam exibidas corretamente no seletor
de widgets, recomendamos manter um arquivo de layout separado designado apenas para a
visualização. Esse arquivo de layout separado inclui o layout real do widget e uma
visualização de coleção de marcador de posição com itens falsos. Por exemplo, é possível imitar um
ListView
fornecendo um marcador de posição LinearLayout
com vários itens de lista
falsos.
Para ilustrar um exemplo de ListView
, comece com um arquivo de layout separado:
// 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>
Especifique o arquivo de layout da visualização ao fornecer o atributo previewLayout
dos
metadados AppWidgetProviderInfo
. Você ainda especifica o layout real do widget
para o atributo initialLayout
e usa o layout real do widget ao
criar uma RemoteViews
no momento da execução.
<appwidget-provider
previewLayout="@layout/widget_previe"
initialLayout="@layout/widget_view" />
Itens de lista complexos
O exemplo na seção anterior fornece itens de lista falsos, porque os itens
da lista são objetos TextView
. Pode ser
mais complexo fornecer itens falsos se eles forem layouts complexos.
Considere um item de lista definido em widget_list_item.xml
e que consiste em
dois 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 fornecer itens de lista falsos, você pode incluir o layout várias vezes, mas isso faz com que cada item de lista seja idêntico. Para fornecer itens de lista exclusivos, siga estas etapas:
Crie um conjunto de atributos para os valores de texto:
<resources> <attr name="widgetTitle" format="string" /> <attr name="widgetContent" format="string" /> </resources>
Use estes atributos para definir o 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>
Crie quantos estilos forem necessários para a visualização. Redefina os valores em 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>
Aplique os estilos nos itens falsos no layout de visualização:
<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>