Создание виджета для Android начинается не с кода, а с понимания архитектуры: виджет — это не самостоятельное приложение, а компонент AppWidget, который работает через RemoteViews и обновляется системой по расписанию или по событию. Если виджет не появляется в списке после установки приложения — почти всегда причина в неправильно объявленном ресивере в манифесте или отсутствии файла метаданных appwidget-provider.
В этом руководстве разберём полный цикл: от создания проекта в Android Studio до обновления данных на виджете и обработки нажатий. Материал ориентирован на разработчиков, знакомых с основами Kotlin и структурой Android-проекта.
Что такое виджет и как он работает
Виджет рабочего стола (App Widget) — это миниатюрный интерфейс приложения, размещаемый на лаунчере. Ключевое ограничение: виджет не может использовать произвольные View. Доступен только ограниченный набор компонентов, поддерживаемых RemoteViews: TextView, ImageView, ProgressBar, ListView и несколько других.
Система управляет виджетом через три связанных элемента:
- 🧩 AppWidgetProvider — BroadcastReceiver, получающий события жизненного цикла виджета (создание, обновление, удаление);
- 📄 AppWidgetProviderInfo — XML-файл с метаданными: размер, период обновления, макет;
- 🎨 Layout — XML-разметка внешнего вида, загружаемая через RemoteViews.
Понимание этой тройки избавляет от большинства ошибок. Например, попытка использовать кастомную View в макете виджета приведёт к тому, что лаунчер покажет пустую область или сообщение об ошибке загрузки.
Подготовка проекта в Android Studio
Откройте существующий проект или создайте новый через File → New → New Project. Отдельного шаблона для виджета не требуется — он добавляется в любое приложение. Самый быстрый способ сгенерировать каркас: File → New → Widget → App Widget. Мастер создаст класс провайдера, XML-метаданные, макет и пропишет ресивер в манифесте.
Если добавляете виджет вручную, проверьте, что в AndroidManifest.xml ресивер объявлен с правильным intent-фильтром:
<receiver android:name=".MyWidgetProvider"
android:exported="false">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/my_widget_info" />
</receiver>
⚠️ Внимание: если в манифесте отсутствует блок meta-data со ссылкой на XML-файл провайдера, виджет не отобразится в системном списке виджетов — это самая частая причина «исчезнувшего» виджета у начинающих разработчиков.
Создание файла метаданных виджета
Файл res/xml/my_widget_info.xml описывает параметры виджета для системы. Без него лаунчер не узнает, какого размера виджет и как часто его обновлять.
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="180dp"
android:minHeight="110dp"
android:updatePeriodMillis="1800000"
android:initialLayout="@layout/widget_layout"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen" />
Обратите внимание на параметр updatePeriodMillis: система не гарантирует точное срабатывание и может объединять обновления для экономии заряда. Для данных, требующих частого обновления (часы, погода в реальном времени), используйте WorkManager или обновление по событию, а не полагайтесь только на периодический таймер.
Вёрстка макета виджета
Макет виджета — обычный XML-файл в res/layout, но с ограничением по поддерживаемым элементам. Разрешены FrameLayout, LinearLayout, RelativeLayout, GridLayout в качестве контейнеров и базовые виджеты вроде TextView, ImageView, Button, ProgressBar.
Пример простого макета с текстом и кнопкой:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="12dp">
<TextView
android:id="@+id/widget_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Загрузка..." />
<Button
android:id="@+id/widget_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Обновить" />
</LinearLayout>
Фон виджета лучше делать с закруглёнными углами через drawable-ресурс: на современных версиях Android система сама обрезает углы, но явный фон даёт предсказуемый результат на разных лаунчерах.
Реализация AppWidgetProvider
Класс провайдера наследуется от AppWidgetProvider и переопределяет колбэки жизненного цикла. Главный из них — onUpdate(), вызываемый при добавлении виджета и при плановых обновлениях.
class MyWidgetProvider : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
for (widgetId in appWidgetIds) {
val views = RemoteViews(context.packageName, R.layout.widget_layout)
views.setTextViewText(R.id.widget_text, "Данные обновлены")
val intent = Intent(context, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
context, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.widget_button, pendingIntent)
appWidgetManager.updateAppWidget(widgetId, views)
}
}
}
Обратите внимание на флаг PendingIntent.FLAG_IMMUTABLE — на новых версиях Android его явное указание обязательно, иначе приложение упадёт с исключением при создании PendingIntent.
Обновление данных и обработка нажатий
Для фонового обновления данных (например, загрузки из сети) используйте WorkManager — это рекомендуемый системой способ отложенных задач. Прямые сетевые запросы из onUpdate() недопустимы: метод выполняется в главном потоке BroadcastReceiver, и длительная операция приведёт к ANR.
Порядок действий для обновления из WorkManager:
- ⚙️ Создайте
Worker, который загружает данные и сохраняет их (например, в SharedPreferences или Room); - 🔄 В конце работы воркера вызовите
AppWidgetManager.notifyAppWidgetViewDataChanged()илиupdateAppWidget(); - 📅 Запланируйте периодическую задачу через
PeriodicWorkRequestс разумным интервалом.
⚠️ Внимание: не храните состояние виджета в статических полях провайдера. Процесс приложения может быть уничтожен системой в любой момент — данные нужно сохранять в постоянное хранилище.
☑️ Проверка перед публикацией виджета
Тестирование и типичные ошибки
Тестируйте виджет на реальном устройстве, а не только на эмуляторе: сторонние лаунчеры (Nova Launcher, фирменные оболочки Samsung, Xiaomi) могут по-разному обрабатывать размеры и обновления. Установите приложение, долгим нажатием на рабочем столе откройте список виджетов и перетащите свой на экран.
| Проблема | Вероятная причина | Решение |
|---|---|---|
| Виджет не отображается в списке | Нет meta-data в манифесте или неверный intent-filter | Проверить объявление ресивера |
| Виджет не обновляется | Слишком малый updatePeriodMillis игнорируется | Использовать WorkManager |
| Кнопка не реагирует на нажатие | PendingIntent создан без FLAG_IMMUTABLE | Добавить обязательный флаг |
| Пустая область вместо виджета | Неподдерживаемый View в макете | Оставить только RemoteViews-совместимые элементы |
| Падение при добавлении виджета | Ошибка в onUpdate() или неверный ID ресурса | Смотреть стектрейс в Logcat |
Отладку удобно вести через Logcat с фильтром по тегу приложения: все исключения в провайдере попадают в системный лог, даже если само приложение не запущено в foreground.
Как сделать виджет со списком (ListView)
Для коллекций используется RemoteViewsService и RemoteViewsFactory — аналог адаптера. Объявите сервис в манифесте с разрешением BIND_REMOTEVIEWS, в макете добавьте ListView, а в провайдере привяжите его через setRemoteAdapter(). Для обновления данных списка вызывайте notifyAppWidgetViewDataChanged().
Полезные рекомендации
Виджет должен быть лёгким и полезным с первого взгляда. Не перегружайте его элементами: пользователь рассчитывает получить ключевую информацию за секунду. Если данных много — показывайте сводку и открывайте полное приложение по нажатию.
Учитывайте тёмную тему: цвета фона и текста должны читаться в обоих режимах. Используйте ресурсы из values-night или системные атрибуты цвета. Также проверьте виджет при разных размерах ячеек — пользователь может растянуть или сжать его, если включён resizeMode.
Частые вопросы
Можно ли сделать виджет без приложения?
Нет. Виджет всегда является частью установленного приложения — он не существует как отдельный APK. Пользователь должен сначала установить приложение, затем добавить виджет на рабочий стол через системный список виджетов.
Почему виджет перестал обновляться после закрытия приложения?
Возможные причины: система ограничила фоновую активность приложения (проверьте настройки батареи), либо обновление завязано на процесс, который был выгружен. Используйте WorkManager и периодический updatePeriodMillis — они переживают выгрузку процесса.
Как добавить настройки для виджета?
Через configuration Activity: укажите её в XML-провайдере атрибутом android:configure. Активность откроется при добавлении виджета и должна вернуть результат с ID виджета, иначе виджет не будет добавлен.
Можно ли использовать Compose в виджетах?
Да, через библиотеку Glance от Jetpack — она позволяет описывать виджеты декларативно в стиле Compose, компилируя результат в RemoteViews. Это современный подход, но требует подключения отдельной зависимости и изучения API Glance.
Какой минимальный размер виджета?
Размер задаётся в ячейках сетки лаунчера через minWidth и minHeight в dp. Ориентировочно одна ячейка соответствует примерно 70dp, но точный пересчёт зависит от устройства — ориентируйтесь на рекомендации официальной документации Android и тестируйте на реальных лаунчерах.