Criar um widget avançado

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 o RemoteViews fornecido anteriormente por um novo RemoteViews. 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 novo RemoteViews com o RemoteViews fornecido anteriormente. Esse método será ignorado se um widget não receber pelo menos uma atualização completa pelo updateAppWidget(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 aciona RemoteViewsFactory.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 a Activity, Broadcast ou Service invocada. Você pode escolher sua própria prioridade. Por exemplo, se você selecionar um Broadcast para o PendingIntent, poderá escolher uma transmissão em primeiro plano para dar a prioridade BroadcastReceiver.

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

Figura 1 : visualização de um widget sem itens de lista.

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:

  1. Crie um conjunto de atributos para os valores de texto:

    <resources>
        <attr name="widgetTitle" format="string" />
        <attr name="widgetContent" format="string" />
    </resources>
    
  2. 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>
    
  3. 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>
    
  4. 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>