Вы разработали виджет с обновлением раз в 30 минут, но пользователи жалуются на устаревшие данные. Курсы валют меняются каждую минуту, статус заказа — в реальном времени. Стандартный updatePeriodMillis с минимумом 30 минут не подходит. Решение — использовать WorkManager для периодических задач или push-уведомления от сервера. Наш опыт — 5+ лет разработки виджетов для Android, более 50 проектов в Google Play. Например, в одном проекте для торговой платформы мы заменили pull-обновление на push через FCM, что сократило задержку с 30 минут до 2 секунд. Пользователи сразу заметили разницу. Мы создаём виджеты любой сложности: от информационных панелей до интерактивных коллекций с мгновенными обновлениями. Гарантируем стабильность и соответствие гайдлайнам. Получите консультацию по вашей задаче.
Как обновлять виджет быстрее 30 минут?
Система разрешает android:updatePeriodMillis не меньше 1800000 мс. Для более частых обновлений используйте WorkManager с PeriodicWorkRequest (минимум 15 минут, установлено в API) или AlarmManager. Внутри WorkManager вызывайте AppWidgetManager.updateAppWidget(). Для push-обновлений — FirebaseMessagingService.onMessageReceived с прямым вызовом updateAppWidget. WorkManager автоматически учитывает Doze Mode и Standby Buckets, чтобы батарея не страдала. Вот сравнение подходов:
| Способ обновления | Минимальный интервал | Рекомендация |
|---|---|---|
| updatePeriodMillis | 30 минут | Только для данных с низкой частотой изменений |
| WorkManager | 15 минут | Для регулярных данных (погода, курсы) |
| Push (FCM) | Мгновенно | Для событийных уведомлений (статус заказа, сообщения) |
Для реализации push-обновлений добавьте серверную интеграцию, отправляющую данные на устройство. Мы используем FCM — push доходят даже при свёрнутом приложении.
Пример реализации обновления через WorkManager
class WidgetUpdateWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
val widgetManager = AppWidgetManager.getInstance(applicationContext)
val widgetIds = widgetManager.getAppWidgetIds(ComponentName(applicationContext, MyWidget::class.java))
val views = buildRemoteViews(applicationContext)
widgetManager.updateAppWidget(widgetIds, views)
return Result.success()
}
}
Почему RemoteViews ограничивает интерактивность?
Android-виджет исполняется в процессе лаунчера, поэтому доступен только RemoteViews. RemoteViews ограничен базовыми View: TextView, ImageView, Button, LinearLayout, RelativeLayout, FrameLayout, GridLayout, ListView, GridView, StackView. Запрещены RecyclerView, ConstraintLayout (до API 31), кастомные View, WebView. Для отображения списков используйте ListView или GridView с RemoteViewsFactory. Начиная с Android 12 (API 31) добавились CheckBox, RadioButton, Switch. Важно: все элементы только через PendingIntent для обработки нажатий — assignOnClick — прямое обращение к View не работает. Типичная ошибка — попытка установить onClickListener напрямую; это невозможно. Используйте setOnClickPendingIntent или setPendingIntentTemplate для коллекций.
Glance: декларативный подход к виджетам
Библиотека Glance предлагает Compose-подобный синтаксис, скрывая ручное создание RemoteViews. Вы пишете декларативно: Column, Text, Button. Glance автоматически генерирует RemoteViews. Это снижает шанс ошибок и ускоряет разработку. Однако Glance пока поддерживает не все компоненты: например, LazyColumn нет, используйте Column с фиксированным числом строк. Состояние управляется через GlanceStateDefinition и updateAppWidgetState. Для нового проекта с minSdkVersion 23+ — это лучший выбор. Вот минимальный пример:
class MyGlanceWidget : GlanceAppWidget() {
@Composable
override fun Content() {
val data = currentState<MyWidgetData>()
Column(modifier = GlanceModifier.fillMaxSize().background(Color.White)) {
Text(text = data.title, style = TextStyle(fontSize = 16.sp))
Button(text = "Обновить", onClick = actionRunCallback<RefreshAction>())
}
}
}
| Характеристика | RemoteViews | Glance |
|---|---|---|
| Синтаксис | Императивный (XML + Java/Kotlin) | Декларативный (Compose-подобный) |
| Вероятность ошибок | Выше | Ниже |
| Поддержка коллекций | ListView/GridView | Column (фиксированное число строк) |
| Минимальный API | 17 (с учётом ограничений) | 23 |
AppWidgetProvider и обработка обновлений
AppWidgetProvider — BroadcastReceiver, который получает обновления. В onUpdate() создайте RemoteViews и вызовите updateAppWidget(). Типичная реализация:
class MyWidget : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
appWidgetIds.forEach { widgetId ->
val views = buildRemoteViews(context)
appWidgetManager.updateAppWidget(widgetId, views)
}
}
}
Для виджетов с данными из интернета используйте фоновый загрузчик в onReceive или через WorkManager. Никогда не блокируйте onUpdate — это UI-поток. Обрабатывайте ошибки загрузки: показывайте заглушку (например, TextView с текстом «Ошибка загрузки») и планируйте повторную попытку через WorkManager.
Работа с коллекциями через RemoteViewsFactory
ListView или GridView в виджете требуют RemoteViewsFactory. Фабрика создаёт RemoteViews для каждого элемента. Нажатия на элементы реализуются через setOnClickFillInIntent и setPendingIntentTemplate. Шаблон — это PendingIntent, который будет запускаться с fill-данными. Пример:
class WidgetListFactory(private val context: Context) : RemoteViewsService.RemoteViewsFactory {
private var items: List<WidgetItem> = emptyList()
override fun onDataSetChanged() {
items = loadDataFromSharedPrefs(context)
}
override fun getViewAt(position: Int): RemoteViews {
val item = items[position]
return RemoteViews(context.packageName, R.layout.widget_list_item).apply {
setTextViewText(R.id.item_title, item.title)
val fillIntent = Intent().putExtra("item_id", item.id)
setOnClickFillInIntent(R.id.item_container, fillIntent)
}
}
}
Важно: onDataSetChanged() вызывается в фоновом потоке, но сам RemoteViewsFactory может быть закэширован. Сбрасывайте кэш при необходимости.
Конфигурация виджета пользователем
AppWidgetConfigure Activity открывается при добавлении виджета. Пользователь выбирает параметры (город, тему, частоту обновления). После выбора обязательно вызвать:
setResult(Activity.RESULT_OK, intent.putExtra(EXTRA_APPWIDGET_ID, widgetId))
Без этого виджет не добавится. В Glance конфигурацию запускайте через GlanceAppWidgetManager().startConfigureActivityIntent.
Этапы разработки виджета
- Анализ требований: определение функциональности, частоты обновлений, целевых размеров (2x2, 4x2, 4x4).
- Проектирование макета: выбор между RemoteViews и Glance, создание XML-макета или Compose-интерфейса.
- Реализация AppWidgetProvider: написание логики обновлений с WorkManager или FCM.
- Интеграция данных: подключение к API, базе данных (Room) или SharedPreferences.
- Тестирование: на эмуляторах и реальных устройствах, включая Doze Mode.
- Публикация: подготовка метаданных, code signing, размещение в Google Play.
Что входит в разработку
- Макет RemoteViews с адаптацией под размеры (2x2, 4x2, 4x4).
- AppWidgetProvider с поддержкой обновлений (WorkManager, FCM).
- Конфигурационный экран при необходимости.
- Интеграция с базой данных (Room) или сетевым API.
- Публикация в Google Play с метаданными, настройка code signing и provisioning profile.
- Документация и исходные коды.
Сроки и стоимость
Срок разработки одного виджета с конфигурацией и регулярными обновлениями — от 3 до 5 дней. Если требуется коллекция с push-обновлениями — до 1 недели. Стоимость рассчитывается индивидуально: отправьте техническое задание для оценки. Получите консультацию по архитектуре — мы поможем выбрать стек (RemoteViews или Glance) и избежать типичных проблем с производительностью. Закажите разработку виджета уже сегодня — свяжитесь с нами для начала работы. Оптимизируйте бюджет — мы предлагаем гибкие условия.
Типичные ошибки при разработке виджетов
- Использование updatePeriodMillis для данных, требующих частого обновления. Переходите на WorkManager или FCM.
- Блокировка onUpdate() долгими операциями. Все фоновые задачи выносите в WorkManager.
- Игнорирование Doze Mode: используйте WorkManager с учётом Standby Buckets.
- Неправильная обработка конфигурации: не забывайте вызывать setResult.
- Отсутствие заглушек при ошибках загрузки данных.
Мы реализуем виджеты на Java или Kotlin, включая гибридные решения. Более 50 успешных проектов подтверждают наш опыт. Свяжитесь с нами для оценки вашего проекта.







