На этой странице описаны рекомендуемые методы создания более продвинутого виджета для лучшего пользовательского опыта.
Оптимизация обновления содержимого виджета
Обновление содержимого виджета может быть затратным с точки зрения вычислений. Чтобы сэкономить заряд батареи, оптимизируйте тип обновления, частоту и время.
Типы обновлений виджетов
Существует три способа обновления виджета: полное обновление, частичное обновление и, в случае виджета-коллекции, обновление данных. Каждый из них имеет различные вычислительные затраты и последствия.
Ниже описан каждый тип обновления и приведены фрагменты кода для каждого из них.
Полное обновление: вызовите
AppWidgetManager.updateAppWidget(int, android.widget.RemoteViews)
для полного обновления виджета. Это заменит ранее предоставленныеRemoteViews
на новыйRemoteViews
. Это самое затратное с точки зрения вычислений обновление.Котлин
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)
Ява
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);
Частичное обновление: вызовите
AppWidgetManager.partiallyUpdateAppWidget
для обновления частей виджета. Это объединяет новыеRemoteViews
с ранее предоставленнымиRemoteViews
. Этот метод игнорируется, если виджет не получает хотя бы одно полное обновление черезupdateAppWidget(int[], RemoteViews)
.Котлин
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)
Ява
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);
Обновление данных коллекции: вызовите
AppWidgetManager.notifyAppWidgetViewDataChanged
, чтобы сделать данные представления коллекции недействительными в вашем виджете. Это запускаетRemoteViewsFactory.onDataSetChanged
. В это время в виджете отображаются старые данные. С помощью этого метода вы можете безопасно выполнять ресурсоемкие задачи синхронно.Котлин
val appWidgetManager = AppWidgetManager.getInstance(context) appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_listview)
Ява
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_listview);
Вы можете вызывать эти методы из любой точки вашего приложения, если приложение имеет тот же UID, что и соответствующий класс AppWidgetProvider
.
Определите, как часто обновлять виджет
Виджеты обновляются периодически в зависимости от значения, указанного для атрибута updatePeriodMillis
. Виджет может обновляться в ответ на взаимодействие с пользователем, широковещательные обновления или и то, и другое.
Периодически обновлять
Вы можете контролировать частоту периодического обновления, указав значение для AppWidgetProviderInfo.updatePeriodMillis
в XML appwidget-provider
. Каждое обновление запускает метод AppWidgetProvider.onUpdate()
, в котором вы можете разместить код для обновления виджета. Однако рассмотрите альтернативы для обновлений широковещательного приемника, описанные в следующем разделе, если ваш виджет должен загружать данные асинхронно или требует более 10 секунд для обновления, поскольку по истечении 10 секунд система считает BroadcastReceiver
неотзывчивым.
updatePeriodMillis
не поддерживает значения менее 30 минут. Однако, если вы хотите отключить периодические обновления, вы можете указать 0.
Вы можете разрешить пользователям настраивать частоту обновлений в конфигурации. Например, они могут захотеть, чтобы биржевой тикер обновлялся каждые 15 минут или только четыре раза в день. В этом случае установите updatePeriodMillis
на 0 и используйте вместо этого WorkManager
.
Обновление в ответ на взаимодействие с пользователем
Вот несколько рекомендуемых способов обновления виджета на основе взаимодействия с пользователем:
Из активности приложения: напрямую вызовите
AppWidgetManager.updateAppWidget
в ответ на взаимодействие с пользователем, например, на нажатие пользователем.Из удаленных взаимодействий, таких как уведомление или виджет приложения: создайте
PendingIntent
, затем обновите виджет из вызванногоActivity
,Broadcast
илиService
. Вы можете выбрать свой собственный приоритет. Например, если вы выбираетеBroadcast
дляPendingIntent
, вы можете выбрать трансляцию переднего плана , чтобы дать приоритетBroadcastReceiver
.
Обновление в ответ на трансляцию события
Примером широковещательного события, требующего обновления виджета, является момент, когда пользователь делает фотографию. В этом случае вы хотите обновить виджет при обнаружении новой фотографии.
Вы можете запланировать задание с помощью JobScheduler
и указать трансляцию в качестве триггера, используя метод JobInfo.Builder.addTriggerContentUri
.
Вы также можете зарегистрировать BroadcastReceiver
для трансляции, например, прослушивая ACTION_LOCALE_CHANGED
. Однако, поскольку это потребляет ресурсы устройства, используйте это с осторожностью и прослушивайте только определенную трансляцию. С введением ограничений трансляции в Android 7.0 (уровень API 24) и Android 8.0 (уровень API 26) приложения не могут регистрировать неявные трансляции в своих манифестах, за некоторыми исключениями .
Что следует учитывать при обновлении виджета из BroadcastReceiver
Если виджет обновляется с помощью BroadcastReceiver
, включая AppWidgetProvider
, учтите следующие соображения относительно продолжительности и приоритета обновления виджета.
Продолжительность обновления
Как правило, система позволяет широковещательным приемникам, которые обычно работают в основном потоке приложения, работать до 10 секунд, прежде чем считать их неотзывчивыми и вызывать ошибку Application Not Responding (ANR). Чтобы избежать блокировки основного потока при обработке трансляции, используйте метод goAsync
. Если обновление виджета занимает больше времени, рассмотрите возможность планирования задачи с помощью WorkManager
.
Caution: Any work you do here blocks further broadcasts until it completes,
so it can slow the receiving of later events.
Более подробную информацию см. в разделе «Соображения безопасности и передовой опыт» .
Приоритет обновления
По умолчанию трансляции, включая те, которые сделаны с помощью AppWidgetProvider.onUpdate
, запускаются как фоновые процессы. Это означает, что перегруженные системные ресурсы могут вызвать задержку в вызове приемника трансляций. Чтобы приоритизировать трансляцию, сделайте ее процессом переднего плана.
Например, добавьте флаг Intent.FLAG_RECEIVER_FOREGROUND
к Intent
, передаваемому в PendingIntent.getBroadcast
, когда пользователь нажимает на определенную часть виджета.
Создавайте точные предварительные просмотры, включающие динамические элементы

В этом разделе описывается рекомендуемый подход к отображению нескольких элементов в предварительном просмотре виджета для виджета с представлением коллекции , то есть виджета, который использует ListView
, GridView
или StackView
.
Если ваш виджет использует один из этих видов, создание масштабируемого предварительного просмотра путем прямого предоставления фактического макета виджета ухудшает опыт, когда предварительный просмотр виджета не отображает никаких элементов. Это происходит, потому что данные представления коллекции устанавливаются динамически во время выполнения, и это выглядит примерно так, как показано на рисунке 1.
Чтобы предварительные просмотры виджетов с представлениями коллекций отображались правильно в селекторе виджетов, мы рекомендуем поддерживать отдельный файл макета, предназначенный только для предварительного просмотра. Этот отдельный файл макета включает в себя реальный макет виджета и представление коллекции-заполнителя с поддельными элементами. Например, вы можете имитировать ListView
, предоставив заполнитель LinearLayout
с несколькими поддельными элементами списка.
Чтобы проиллюстрировать пример для ListView
, начнем с отдельного файла макета:
// 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>
Укажите файл макета предварительного просмотра при предоставлении атрибута previewLayout
метаданных AppWidgetProviderInfo
. Вы по-прежнему указываете фактический макет виджета для атрибута initialLayout
и используете фактический макет виджета при построении RemoteViews
во время выполнения.
<appwidget-provider
previewLayout="@layout/widget_previe"
initialLayout="@layout/widget_view" />
Сложные элементы списка
Пример в предыдущем разделе предоставляет поддельные элементы списка, поскольку элементы списка являются объектами TextView
. Может быть сложнее предоставить поддельные элементы, если элементы являются сложными макетами.
Рассмотрим элемент списка, который определен в widget_list_item.xml
и состоит из двух объектов 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>
Чтобы предоставить поддельные элементы списка, вы можете включить макет несколько раз, но это приведет к тому, что каждый элемент списка будет идентичным. Чтобы предоставить уникальные элементы списка, выполните следующие действия:
Создайте набор атрибутов для текстовых значений:
<resources> <attr name="widgetTitle" format="string" /> <attr name="widgetContent" format="string" /> </resources>
Используйте эти атрибуты для задания текста:
<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>
Создайте столько стилей, сколько необходимо для предварительного просмотра. Переопределите значения в каждом стиле:
<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>
Примените стили к поддельным элементам в макете предварительного просмотра:
<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>