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
A atualização do conteúdo do widget pode ter um custo computacional elevado. Para economizar bateria, otimize o tipo, a frequência e o tempo de atualização.
Tipos de atualização de widgets
Há três maneiras de atualizar um widget: atualização completa, atualização parcial e, no caso de um widget de coleta, atualização de dados. Cada um tem custos computacionais e ramificações diferentes.
Confira a seguir a descrição de cada tipo de atualização e os snippets de código para cada uma.
Atualização completa:chame
AppWidgetManager.updateAppWidget(int, android.widget.RemoteViews)
para atualizar totalmente o widget. Isso substitui oRemoteViews
fornecido anteriormente por um novoRemoteViews
. Essa é a atualização mais dispendiosa em termos computacionais.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 será 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 de coleta:chame
AppWidgetManager.notifyAppWidgetViewDataChanged
para invalidar os dados de uma visualização de coleção no seu widget. Isso acionaRemoteViewsFactory.onDataSetChanged
. Enquanto isso, os dados antigos são exibidos no widget. Você pode executar tarefas pesadas de maneira segura de forma síncrona 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 de qualquer lugar no 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, atualizações de transmissão ou ambas.
Atualizar periodicamente
É possível controlar a frequência da atualização periódica especificando um valor para
AppWidgetProviderInfo.updatePeriodMillis
no XML de appwidget-provider
. Cada
atualização aciona o método AppWidgetProvider.onUpdate()
, em que você
coloca o código para atualizar o widget. No entanto, considere as alternativas para
atualizações de broadcast receiver descritas na
seção a seguir se o widget precisar carregar dados de forma assíncrona ou levar mais
de 10 segundos para ser atualizado, porque após esse período, o sistema considera um
BroadcastReceiver
como não responsivo.
updatePeriodMillis
não aceita valores de menos de 30 minutos. No entanto, se
você quiser desativar as atualizações periódicas, especifique o valor "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 código 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
Estas sã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.Com interações remotas, como uma notificação ou um widget de app:crie um
PendingIntent
e atualize-o usando aActivity
,Broadcast
ouService
invocada. 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 a prioridadeBroadcastReceiver
.
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
acionador 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 esse método 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 com base em um BroadcastReceiver
, incluindo
AppWidgetProvider
, esteja ciente das seguintes considerações sobre a
duração e a prioridade de uma atualização de widget.
Duração da atualização
Como regra, 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 acionar um erro do tipo O app não está respondendo (ANR). Se levar mais tempo para atualizar o widget, considere estas alternativas:
Agende uma tarefa usando o
WorkManager
.Dê mais tempo ao destinatário 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 com
AppWidgetProvider.onUpdate
, são executadas como processos em segundo plano. Isso significa
que recursos sobrecarregados do sistema podem causar um atraso na invocação do broadcast
receiver. Para priorizar a transmissão, torne-a um processo de primeiro plano.
Por exemplo, adicione a flag
Intent.FLAG_RECEIVER_FOREGROUND
ao Intent
transmitido ao PendingIntent.getBroadcast
quando o usuário
toca em uma determinada parte do widget.
Criar visualizações precisas que incluam 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
ListView
, GridView
ou StackView
.
Caso o widget use uma dessas visualizações, criar uma visualização escalonável fornecendo diretamente o layout real do widget prejudicará a experiência quando a visualização do widget não exibe itens. Isso ocorre porque os dados de visualização da coleção são definidos dinamicamente no momento da execução e são parecidos com a imagem mostrada na Figura 1.
Para que as visualizações de widgets com visualizações de coleção sejam exibidas corretamente no seletor, 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 marcadores com itens falsos. Por exemplo, é possível imitar uma
ListView
fornecendo um marcador LinearLayout
com vários itens de lista
falsos.
Para ilustrar um exemplo de uma 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 de 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 um RemoteViews
durante a execução.
<appwidget-provider
previewLayout="@layout/widget_previe"
initialLayout="@layout/widget_view" />
Itens de lista complexos
O exemplo na seção anterior mostra itens de lista falsos, porque os itens
da lista são objetos TextView
. Pode ser
mais complexo fornecer itens falsos se os itens forem layouts complexos.
Considere um item de lista definido em widget_list_item.xml
e composto por
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 da 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 aos 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>