* Este post foi redigido pela equipe C.E.S.A.R. Sorocaba
Neste post, pretendemos mostrar em mais detalhes a criação de um App Widget, ou Companion Widget (aquelas aplicações que podem ser colocadas na tela inicial do dispositivo). Veremos a criação de uma Activity para configuração do Widget, assim como a sua atualização, e a interação com o usuário. Um Widget pode ser adicionado várias vezes à tela, ou seja, pode haver várias instâncias do mesmo Widget sendo exibidas, e iremos tratar os eventos de forma distinta para cada uma delas.
Num post anterior, vimos que para construir um App Widget é necessário:
- criar uma subclasse de AppWidgetProvider, que será responsável pelo tratamento dos eventos do Widget;
- criar um arquivo XML de metadados do Widget;
- declarar o widget no AndroidManifest.xml;
- criar o layout do Widget, seguindo as limitações de uma RemoteView.
A classe AppWidgetProvider é uma sub-classe de BroadcastReceiver. Por este motivo, o ciclo de vida de um Widget é similar ao de um Broadcast Receiver, e não ao de uma Activity, além de ser necessário declarar os Widgets existentes do projeto como <receiver> no arquivo AndroidManifest.xml.
Como já foi citado naquele post, há cinco métodos de AppWidgetProvider que podem ser sobrescritos no nosso Widget, e cada um é executado em resposta a um broadcast específico (identificados aqui por constantes herdadas daquela classe):
- onEnabled(): Executado ao adicionar a primeira instância de um Widget na tela, em resposta ao broadcast ACTION_APPWIDGET_ENABLED. Pode ser utilizado, por exemplo, para fazer alguma configuração global da aplicação;
- onDisabled(): Executado ao remover a última instância do Widget da tela, como resposta ao broadcast ACTION_APPWIDGET_DISABLED. Pode ser usado para liberar algum recurso global/compartilhado da aplicação;
- onDeleted(): Executa sempre que uma instância do Widget é removida da tela, em resposta ao broadcast ACTION_APPWIDGET_DELETED. Neste método, podem ser liberados recursos utilizados por uma única instância;
- onUpdate(): Executa em resposta ao broadcast ACTION_APPWIDGET_UPDATE, ou seja, sempre que o Widget é adicionado à tela, quando expira o tempo configurado no atributo updatePeriodMillis do AppWidgetProviderInfo, ou quando o sistema é reiniciado. Segundo artigo do próprio site do Android, caso uma Activity de configuração tenha sido declarada, este método não é executado ao adicionar o Widget, mas apenas nas próximas atualizações. Desta forma, a Activity de configuração deve ser responsável por executar a primeira atualização do Widget;
- onReceive(): É executado em resposta a todos os broadcasts recebidos, e antes de cada um dos métodos acima. Na maioria dos casos, não é necessário sobrescrever este método, uma vez que todos os broadcasts já são filtrados pelo mesmo, na superclasse.
Apenas para demonstrar os conceitos abordados, iremos criar uma aplicação que exibe Toasts na tela do dispositivo, em intervalos configurados, além de alterar um TextView com a quantidade de atualizações ocorridas.
Criamos o projeto, desmarcando a opção para criar uma Activity (o que iremos fazer apenas para a configuração do nosso Widget). Adicionamos então uma nova classe ao nosso pacote, com o nome de WidgetProvider, extendendo a classe AppWidgetProvider. Por enquanto, sobrescrevemos o método onUpdate.
1 2 | @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { } |
Os parâmetros deste método são:
- context: o Contexto atual do nosso broadcast receiver;
- appWidgetManager: objeto que pode ser usado para atualizar ou obter informações sobre widgets;
- appWidgetIds: ids das instâncias deste widget que precisam ser atualizadas.
Como os eventos do widget serão tratados de forma distinta entre as suas instâncias, usaremos o parâmetro appWidgetIds para identificar a instância que os gerou, pois o Android reutiliza os Intents que tenham valores de Action e de Scheme semelhantes.
Em seguida, adicionamos o layout de nosso Widget, num arquivo com nome layout_widget.xml. Usaremos um layout simples, exibindo apenas um TextView com a quantidade de vezes Toasts exibidos a partir daquela instância:
<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout
xmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
android:gravity=”center”>
<TextView
android:layout_width=”fill_parent”
android:text=”@string/txtatualizacao”
android:layout_height=”wrap_content”
android:id=”@+id/txtatualizacao”
android:textColor=”#000000″ />
<Button
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:text=”Atualizar”
android:id=”@+id/btatualizar” />
<Button
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:text=”Resetar”
android:id=”@+id/btresetar” />
</LinearLayout>
Adicionamos agora o arquivo de meta-dados do Widget, aqui nomeado provider_info.xml. Os atributos da tag appwidget-provider devem ser compatíveis com os da classe AppWidgetProviderInfo. Neste exemplo iremos controlar o intervalo de atualização pela Activity de configuração. Portanto, omitimos o atributo updatePeriodMillis deste arquivo:
<?xml version=”1.0″ encoding=”utf-8″?>
<appwidget-provider xmlns:android=”http://schemas.android.com/apk/res/android”
android:minWidth=”146dp” android:minHeight=”146dp”
android:updatePeriodMillis=”86400000″
android:initialLayout=”@layout/layout_widget”
android:configure=”cesar.org.br.WidgetConfiguration”>
</appwidget-provider>
Caso fôssemos utilizar este o atributo updatePeriodMillis, o intervalo mínimo para atualização seria de 30 minutos, como forma de poupar a bateria do dispositivo. No arquivo acima, foram definidas também as dimensões mínimas do Widget, nos atributos minWidth e minHeight, e qual o layout a ser utilizado, em initialLayout.
Agora, é preciso declarar o Widget como um receiver no AndroidManifest.xml de nosso projeto, dentro da seção <application>:
<receiver
android:name=”.ToastWidgetProvider”>
<intent-filter>
<action android:name=”android.appwidget.action.APPWIDGET_UPDATE” />
</intent-filter>
<meta-data
android:name=”android.appwidget.provider”
android:resource=”@xml/imageswidget_info” />
</receiver>
Assim como no post anterior, adicionamos um Intent Filter com a Action APPWIDGET_UPDATE, e o nosso arquivo de meta-dados.
O próximo passo é criar a classe de configuração que tem o objetivo de proporcionar um mecanismo de configuração do widget.
As configurações podem ser de diversas espécies, como de mudanças de configuração de views que estão dentro do widget, propriedades do widget ou adição de uma view no widget.
Seguindo nosso exemplo iremos criar uma classe de configuração chamada WidgetConfiguration, ela deve ser herdar da classe Activity:
1 2 | import android.app.Activity; public class WidgetConfiguration extends Activity { } |
Como já visto em outros posts, uma activity deve ser declarada no manifesto, entretanto, além dessa configuração iremos fazer mais um passo no caso da nossa classe de configuração.
No manifesto teremos que adicionar o trecho do xml de configuração:
<activity
android:name=”.WidgetWidgetConfiguration”
android:label=”@string/app_name”>
<intent-filter>
<action
android:name=”android.appwidget.action.APPWIDGET_CONFIGURE” /> </intent-filter>
</activity>
Observe que diferente de uma Activity declarada de forma convencional no manifesto, ela possui um intent filter contendo uma ação que indica ser uma classe que configura um widget.
O próximo passo é indicar no xml de meta-dados que existe uma classe de configuração, para isso devemos adicionar um atributo:
android:configure=”cesar.org.br.ToastWidgetConfigure”
Por fim, para terminarmos a classe de configuração iremos implementar o método onCreate da Activity.
1 2 3 4 | @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } |
Existem duas maneiras de atualizar um widget, sendo a primeira delas a mais simples. Para isso basta atribuir um valor em milisegundos para o parâmetro updatePeriodMillis no AppWidgetProviderInfo, que, como já foi dito anteriormente, só aceita valores maiores que 30 minutos.
Para atualizar um widget com intervalos menores do que 30 minutos, é necessário criar um alarme. Um alarme é um serviço do android que permite que broadcasts sejam enviados para o sistema através do Intent registrado para o alarme. Para criar um alarme é necessário criar o Intent com os parâmetros relativos ao widget criado que será chamado cada vez que o tempo do alarme esgotar. A ação do Intent deverá ser AppWidgetManager.ACTION_APPWIDGET_UPDATE, que é a ação responsável por atualizar um widget.
1 2 | AlarmManager alarm = (AlarmManager) getSystemService(Context.ALARM_SERVICE); alarm.setRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime(), segundos * 1000, pendingIntent); |
Além da ação, é necessário informar ao Intent os IDs dos widgets a serem atualizados por esse Intent. Isso é feito colocando um vetor com os IDs que serão atualizados no extra AppWidgetManager.EXTRA_APPWIDGET_IDS do Intent. Também é necessário enviar a URI do seu widget, que faz com que apenas o seu widget receba a notificação. Caso a URI não seja enviada, todos os widgets do sistema serão notificados.
1 2 3 4 | Intent intent = new Intent(); intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); intent.setData(Uri.parse("widgettoast://widget/id/" + appWidgetId)); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int [] { appWidgetId } ); |
Com o Intent criado com todos parâmetros corretos, basta criar um PendingIntent com esse Intent. Para finalmente criar o alarme, basta obter o AlarmManager do sistema, e então chamar o método setRepeating desse AlarmManager, passando os parâmetros necessários, entre eles o tipo do alarme, o PendingIntent da operação e o intervalo de tempo desejado.
1 | PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); |
Vale lembrar que os alarmes registrados continuam executando mesmo após a remoção do widget. Portanto devemos cancelar esse alarme no método onDeleted do widget, que removerá o alarme relacionado à instância do widget removida.
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Override public void onDeleted(Context context, int[] appWidgetIds) { super.onDeleted(context, appWidgetIds); for (int appWidgetId : appWidgetIds) { Intent intent = new Intent(); intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); intent.setData(Uri.parse("widgettoast://widget/id/" + appWidgetId)); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0); AlarmManager alarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); alarm.cancel(pendingIntent); } } |
Confira abaixo o código das principais classes e arquivos do projeto, e o resultado na tela do dispositivo:
Widget Provider
Activity de Configuração
AndroidManifest.xml
XML de meta-dados
Widget Layout
Apesar dos diversos detalhes envolvidos, a criação de um App Widget não é tarefa das mais complicadas. Aplicando conceitos como Intents, Broadcast Receivers, Activities, e boas práticas de desnvolvimento para Android, é possível construir aplicações ricas em conteúdo, com a melhor performance possível.

home









blog de design do c.e.s.a.r.