Помощь в создании скрипта автоматической конфигурации Windows 10 [закрыт]
Закрыт. Данный вопрос необходимо конкретизировать. Ответы на него в данный момент не принимаются.
Хотите улучшить этот вопрос? Переформулируйте вопрос так, чтобы он был сосредоточен только на одной проблеме.
Закрыт 2 года назад .
Поскольку настройка, например 10 компов, на данный момент занимает от 6ти часов и выше(в зависимости от качества удалённого соединения) и даже последовательное прокликивание настроек на одновременно 4х машинах дело не ускоряет, было принято решение сваять скриптик во благо тайм-менеджмента С большего это всего лишь автоматизация дебильных копаний в настройках системы, фаервола, таск менеджера и прочих базовых потребностей. В идеале от меня хотят видеть «одну волшебную кнопку которая сама сделает всё за пару минут» Начал я исесно с гугления и поиска скриптов по каждому пункту Что должно быть включено в основной скрипт: Создать пользователя с фиксированным именем и паролем task scheduler 3am reboot (shutdown /r /f /t 0) Установка нашего фирменного «Агента» и добавление всех его компонентов в shell:startup и windows defender firewall отключение обновлений с помощью wu10man или каким-нибудь другим способом (wu10man во всяком случае делал именно то, что там надо) Настройка параметров windows: security at a glance — всё должно быть отключено (no actions needed) отключение bluetooth отключение уведомлений отключение впадения компьютера в спячку отключение cortana и мусора в Task Manager (Disable «microsoft OneDrive’) (Для всего этого я нашел Sophia Script. Думаю, что можно «повыдирать» только необходимое) Установка сплошного цвета Autologon «users must enter a user name . » must be disabled Установка дополнительного ПО (Remotix Agent, Log me in) Краткая суть вопроса такова: Объеденить несколько скриптов в один Поскольку я ничерта не понимаю в программировании, синтаксис для меня это тёмный лес, а задачу надо выполнить в достаточно сжатые сроки (дедлайн уже в пятницу), времени на детальное изучение вопроса просто нет Единственное, что в моих силах это копипастить команды по очереди(надеясь, что я нашёл именно то, что мне надо и оно будет работать). Однако такой подход никого не устраивает. Жирным выделил то, что удалось таки найти Остальное под вопросом Очень прошу — помогите хоть советом, хоть ссылкой решить данную проблему.
Облако OpenStack: мифы и реальность
Проект OpenStack возник по инициативе двух производителей и объединил их продукты – объектное хранилище Rackspace и систему управления гипервизорами NASA. «OpenStack призван помочь организациям предоставлять облачные вычислительные ресурсы, запущенные на стандартном оборудовании», – заявляют вендоры. В данном материале мы попробуем разобраться, так ли это на самом деле.
OpenStack сегодня
Первый релиз OpenStack вышел в октябре 2010 г. Так компания Rackspace стала одним из ближайших конкурентов другого облачного провайдера IaaS – Amazon Web Services (AWS), который в то время сильно терял свои позиции. По сути, OpenStack – копия AWS: многие сервисы и процессы в этих платформах одинаковы и даже совместимы между собой.
IaaS: OpenStack vs. Amazon Web Services

- средство управления гипервизорами (Nova)
- объектное хранилище (Swift)
- хранилище образов виртуальных машин (Glance)
- веб-интерфейс управления (Horizon)
- каталог пользователей и сервисов (Keychain)
- средство управления сетевой инфраструктурой (Neutron)
- блочное хранилище (Cinder)
- оркестратор облачных приложений (Heat)
- инструмент учета потраченных ресурсов (Ceilometer)
- средство предоставления БД как услуги.
О мифах
Является ли OpenStack технологией будущего, мейнстримом? Как знать. Возможно, это такой же неизведанный пока Клондайк, как Linux начала 90-х гг, когда мало кто о нем знал и совсем немногие видели. OpenStack обладает большим потенциалом, но на сегодня трудно назвать его готовым продуктом. И вот почему.
Функционал решения постоянно трансформируется, дополняется и документация не всегда успевает за этими изменениями. Зачастую в новом релизе появляются возможности, которые не отражены в инструкции к продукту.
Поиск неисправностей сильно затруднен, так как обычно в ответ на любые ошибки OpenStack выдает несколько килобайт стектрейсов. Приходится тратить немало времени и сил только для того, чтобы определить причину сбоя.
Внедрение OpenStack требует серьезной работы программистов. Если вы собираетесь продавать облачные услуги на основе данного продукта (пусть даже в рамках одной своей компании), имеющегося в нем функционала будет явно недостаточно. Например, вы не сможете ограничить сетевой трафик одной виртуальной машины так, чтобы она не мешала другим. Базовые возможности OpenStack не позволяют плавно регулировать потребляемые ресурсы и даже мигрировать виртуальную машину на другой хост. Средств мониторинга доступности тоже нет. Наконец, хороший пример готовности OpenStack как продукта – процедура добавления и удаления узлов с гипервизорами. Добавляются они определенной процедурой, а вот для удаления compute-ноды нет специальной команды. Приходится идти в специальную таблицу MySQL и делать это вручную:
mysql> delete from compute_nodes where hypervisor_hostname='nova4’; Query OK, 1 row affected (0.00 sec) mysql> delete from services where host='nova4’; Query OK, 1 row affected (0.00 sec) mysql>
Безвозмездно, то есть даром? Скачать OpenStack вы можете абсолютно бесплатно. Однако в силу неготовности продукта его «бесплатность» выливается в серьезные трудозатраты в части доработки.
Разрабатывается свободным сообществом? На сайте OpenStack можно посмотреть статистику: кто и в какой степени участвовал в разработке кода решения.
За всю историю существования OpenStack

Больше остальных отличилась компания Rackspace, зарабатывающая на предоставлении пользователям инфраструктуры как услуги. Далее идут Red Hat, которая занимается продажами, внедрением и поддержкой решений OpenStack; затем – HP и IBM, предоставляющие облачные услуги на базе OpenStack. Как видим, доля свободных программистов-энтузиастов (OpenStack Foundation) минимальна, а по количеству пользователей это всего 5–10 человек за все 4 года.
Релиз Juno (октябрь 2014)

Если смотреть на последнее полугодие 2014-го – картина несколько изменилась, но лидеры остались практически те же. Плюс появились новые игроки – Mirantis, продающая только OpenStack (инсталляции и поддержку), а также компания SUSE. Замыкают список производители, у каждого из которых собственные цели. NEC обеспечивает поддержку своего оборудования, работающего с OpenStack. VMware пытается защитить свою экосистему, «возглавив» реализацию совместимости OpenStack с ESXi.
С OpenStack у меня будет свой Amazon? Является ли данная технология ключевым фактором успеха для облачного провайдера? Нет, не является. Это хорошо видно по магическому квадранту Гартнера. Подавляющее число компаний используют VMware, чуть менее популярен KVM. А вот платформе Xen отдали предпочтение как IBM, Fujitsu и GoGrid, так и явный лидер Amazon. Понятно, что дело вовсе не в гипервизоре.

Можно ли заменить традиционные виртуальные машины облачными? Особенности двух подходов к виртуализации отражены в сравнительной таблице (спасибо Red Hat за нее).
| Традиционные ВМ ( ESXi, RHEV) | Облачные ВМ (OpenStack, AWS) |
|---|---|
| Большие. Данные хранятся внутри ВМ | Маленькие. Данные хранятся снаружи ВМ |
| 1 приложение — 1 ВМ | 1 приложение — много ВМ |
| Цикл жизни ВМ – годы | Цикл жизни ВМ – от часов до месяцев (приложения находятся не внутри ВМ, а снаружи) |
| Увеличить ту или иную ВМ с приложением можно лишь до размеров ее хоста | При увеличении/уменьшении числа пользователей можно динамически создавать/удалять ВМ |
| Вертикальное масштабирование (наращивается производительность ВМ) | Горизонтальное масштабирование (увеличивается количество ВМ) |
| Приложение находится внутри и не переживет сбой ВМ, т.е. важно обеспечить отказоустойчивость | В случае сбоя ВМ облачное приложение создает ее с нуля, оставаясь в рабочем состоянии |
| SLA требует обеспечения доступности приложения через Live Migration, HA и другой подобный функционал | SLA требует возможности добавления/удаления экземпляров ВМ для поддержания доступности приложения |
Эксплуатация. В OpenStack нет средств обеспечения высокой доступности и конкретных планов по ее реализации. Системы резервного копирования для ВМ также нет. Если сервер виртуализации вышел из строя, то и виртуальные машины мы потеряли – придется создавать новые (и это нормально).
Рекламируется, что OpenStack работает во всех гипервизорах, однако это не так. Основная система управления, по которой ведется разработка и тестирование продукта, – KVM. Остальные либо тестируются не полностью (например, Microsoft Hyper-V, гипервизоры VMware, Citrix XenServer 6.2), либо не тестируются вовсе (Baremetal, Docker, LXC через libvirt, Xen через libvirt) и их работа в OpenStack не может быть гарантирована. В последнем случае при запуске вы увидите сообщение вроде: «The libvirt driver is not tested on xen/x86_64 by the OpenStack project and thus its quality can not be ensured».
При этом возможность живой миграции (Live Migration), т.е. переноса ВМ с одного физического сервера на другой без остановки сервисов, в OpenStack отключена по умолчанию. Несмотря на то, что данный функционал поддерживается основным гипервизором – KVM.
Применение OpenStack
Несмотря на обозначенные особенности и недостатки, платформу OpenStack вполне можно использовать для различных задач. Один из вариантов: у вас уже есть какая-то облачная среда (например, тот же Amazon или Rackspace), но вы хотите переехать, развернув облачные сервисы у себя.
Еще один сценарий: вы планируете стать облачным провайдером, у вас есть стартап, достаточно финансов, программисты, желание работать и самое главное – какие-то ноу-хау, которые вы развернете в этой облачной среде. Т.е. вы собираетесь сделать не второй Google или Facebook, а нечто абсолютно новое. В этом случае OpenStack – тот продукт, на основе которого можно далее развиваться.
Если есть регулярная потребность в тестировании или нужна облачная платформа для разработки нового приложения, также есть смысл обратить внимание на OpenStack. Как вариант, можно задействовать только какой-то отдельный модуль OpenStack. Например, используя труд нескольких программистов, написать и реализовать нечто вроде корпоративного Dropbox на основе Swift.
- инфосистемы джет
- openstack
- clouds
- iaas
- open source
- инфраструктурные решения
- Блог компании Инфосистемы Джет
- Open source
Вы отправили слишком много запросов, поэтому ваш компьютер был заблокирован.
Для того, чтобы предотвратить автоматическое считывание информации с нашего сервиса, на Linguee допустимо лишь ограниченное количество запросов на каждого пользователя.
Пользователям, браузер которых поддерживает Javascript, доступно большее количество запросов, в отличие от пользователей, чей браузер не поддерживает Javascript. Попробуйте активировать Javascript в настройках вашего браузера, подождать несколько часов и снова воспользоваться нашим сервером.
Если же ваш компьютер является частью сети компьютеров, в которой большое количество пользователей одновременно пользуется Linguee,сообщитеоб этом нам.
Виджеты: Android против iOS

Долгое время виджеты были отличительной чертой Android, пока на айфонах царили стройные ряды одинаковых иконок. Но в 2020-м они масштабно пришли и на iOS. Обычно мобильные разработчики знают одну из этих платформ, но у Анны Жарковой (@anioutka) есть опыт работы с обеими — и она выступила у нас с докладом о виджетах на обеих.
Сейчас мы готовим конференцию Mobius 2023 Spring, где также освещаются обе платформы, а Анна выступит с новым докладом. И в ожидании этого решили сделать для Хабра текстовую версию доклада о виджетах (видеозапись также прилагаем). Далее — текст от лица спикера.
Вступление
Что будет в докладе? Сначала мы рассмотрим, что такое виджеты в целом. Казалось бы, тема известная, но далеко не все с ними успели поработать. Многих смущает сама терминология, потому что в Flutter тоже есть понятие «виджеты», и там это совершенно другое.
Поговорим про то, какими были классические виджеты до Android 12, с которых всё и начиналось. Затем про Glance — специальный Compose-фреймворк для создания виджетов, его возможности и ограничения. Так как мы хотим рассмотрим, как сделать что-то полноценное и интересное, то затронем темы архитектуры, управления состояниями и обновление данных.
Далее посмотрим, что такое виджеты в iOS, как они появились, кто что у кого позаимствовал. Затронем, какие есть особенности реализации под iOS, есть ли ограничения и как их можно обойти. Ну и, разумеется, в рамках iOS тоже рассмотрим решения на практике, и их архитектурные нюансы.
Виджеты: концепция
Итак, что же такое виджеты? Не будем путать это с флаттеровскими контролами. Это специальные компоненты приложения, которые выводится на домашнем экране. Многие видели встроенные виджеты — календарь, погода, новости, часы и так далее.
Что же они нам дают:
- Быстрый доступ к определённой функциональности: например, посмотреть время, узнать текущую погоду.
- Быстрый доступ к основному приложению: по тапу на виджет вы можете быстро перейти в него.
- Ну и, разумеется, это привлекает внимание к основному приложению. Поэтому важно их сделать красивыми и интересными.
Если говорить про историю появления виджетов, то они были представлены на платформе Android ещё в 2009-м, а затем пошло развитие данной технологии:

Стали появляться категории виджетов, затем самые разные настройки отображения. Далее возникла глобальная пауза в развитии данной технологии. Казалось бы, всё уже достигнуто, и сами виджеты стали использоваться меньше.
Затем в 2018-м про эту фичу снова вспомнили, решили реанимировать и стали прорабатывать возможность конфигурации функциональности виджетов и их вида.
В 2020 году появляются современные iOS-виджеты. На самом деле, первые виджеты iOS появились ещё в 2017 году, но в 2020-м пережили новый рывок, о чём мы ещё поговорим.
И, наконец, в 2021-м с выходом Android 12 произошло глобальное изменение концепции виджетов, снова вернувшее популярность этой фиче и открывшее нам довольно много интересных возможностей.
Начнём с Android
Давайте посмотрим, как же всё развивалось на Android, начиная с ранних версий.

Изначально виджет состоял из следующего:
- RemoteView — обертка над классическим View, которая использовала XML layout, потому что тогда не было Compose.
- XML для описания конфигураций самого виджета.
- Специальный AppWidgetProvider, который определял поведение виджета.
- Менеджер для обновления виджета, чтобы он брал новую информацию в зависимости от событий.
- События декларировались в манифесте и для их оповещения использовался механизм broadcast receiver.
Весь UI реализовывался в XML. На скриншоте представлен пример виджета, который я писала еще в 2015 году. Это было приложение календаря с поддержкой интерактивных виджетов с изменяемой темой.

Всё это сделано на XML. Нет никаких дополнительных анимаций.
Также, чтобы виджет корректно работал, необходимо было в XML прописать провайдер виджета с указанием нужной для настройки информации.
Это и сведения о начальном layout, и ссылка на previewImage в ресурсах (изначально @drawable, а потом XML). Также указывались размеры виджета. Была целая шкала для измерения размера виджета. По ней экран делился на доли в 72dp, и нужно было рассчитывать ширину и высоту относительно кратности этой величине. Также можно было указать период обновления виджета, либо поставить 0, если хотелось использовать свой собственный механизм для оповещения об изменениях.
Также, чтобы виджет реагировал на изменения, нужно было создать выделенный receiver и в указать action для обновления конфигурации виджета.
Архитектура сложного решения с виджетами включала следующие компоненты:

Помимо AppWidget и AppWidget Receiver, в неё могли входить специальные сервисы, если вам нужна была дополнительная логика (например, отложенные или долгие задачи). При необходимости сервис мог «стучаться» в репозиторий. Для передачи информации из сервиса ресиверу использовался броадкаст. Его отправка могла осуществляться из AlarmManager. Затем при получении информации в менеджере виджета нужно было вызвать метод updateAppWidget для обновления и отрисовки.
Сложность реализации виджета зависит от его визуальной составляющей и бизнес-логики. Концептуально всю сложную бизнес-логику следует выносить в само приложение.
Рассмотрим для примера код AppWidgetProvider календаря, внутри которого логика создания и обновления виджета:
public class SmallCalendarWidget extends AppwWidgetProvider
У него был специальный метод Update, который вызывался при обновлении виджета. Именно здесь и производилось обновление View. Также можно было поставить pending intent для запуска какого-то сервиса (в том числе и по AlarmManager) или, например, открыть какой-то экран по тапу на этот View.
Если говорить про UI, то для старых виджетов было доступно практически всё, кроме анимации и видео. Любые статические элементы, списки, простые layout. Мой виджет календаря, например, был сделан из стандартных элементов:

Чтобы всё работало правильно и красиво, нужно было правильно сверстать и сконфигурировать.
Тот визуал, который не поддерживался из коробки, можно было сделать с помощью последовательности картинок из View. Например, если вы хотели анимированные часы, то нужно было сделать имитацию анимации: раз в секунду создавать view из кода или XML и устанавливать в специальный RemoteView. Скажем, популярные в 2015 году экранные часы делались именно так, потому что никакой анимации быть не могло.
Вот пример кода, где мы сначала рендерим изображение из заготовки View с указанием параметров и устанавливаем в RemoteView. А затем этим RemoteView мы обновляем через manager наш виджет.
val remoteView = RemoteViews(context.getPackageName(), R.layout.widget_layout) val bitmap = Utility.createDrawableFromView(context, createCurrentClockView(context)) remoteView.setImageViewBitmap(bitmap) manager.updateAppwWidget(widgetId, remoteView)
Теперь у нас Compose
С появлением Jetpack Compose несколько изменилась архитектура решения, которую мы использовали. Например, представили фреймворк Glance, который позволяет легко создавать виджеты с помощью Сompose-синтаксиса. Он оперирует специальными WidgetProvider и WidgetReceiver, и работа идет как с помощью RemoteView, так и через рендеринг в XML. Точнее, у вас есть возможность зарендерить в XML как Remote и потом интеропнуть специальным AndroidRemoteView. Под капотом Сomposable также транслируется в тот же RemoteView, который мы использовали раньше. То есть, по сути, это обертка над прежним решением (все DSL так или иначе являются такими обёртками).
Но есть некоторые отличия по сравнению с классическим Compose. Костяк решения представляют специальный GlanceAppWidget и GlanceAppWidgetReceiver. Терминология, казалось бы, похожа на классический виджет, и мы видим все признаки Compose, но на деле это именно Glance с полным своим инструментарием. В AppWidgetReceiver мы напрямую создаём виджет. Рендеринг Composable в View происходит в специальном методе content нашего виджета.
class TimeWidget : GlanceAppWidget() < @Composable override fun Content() < TimeView(. ) >> class TimeWidgetReceiver : GlanceAppWidgetReceiver()
Эти Composable не те же самые, что в Jetpack Compose. Здесь есть и Box, Row, Column, Text, Button, LazyColumn, Image, Space — все элементы для создания статического изображения. Также специальный Modifier. Но это контролы и Modifier Glance, а не Material. Потому что здесь не поддерживается free canvas. В Glance мы оперируем статичными View, которые не слушают сами изменения состояния — по сути, они stateless. Для их обновления есть специальный компонент, которые отвечает как за хранение состояний, так и их обработку, и либо сам, либо по триггеру вызывает перерисовку виджета с новыми вводными. То есть сам виджет автоматически не обновляется.
Итак, Free canvas недоступен, поэтому использовать тот же composable, что в приложении на Compose, вы не можете. В таком случае вы получаете такую некрасивую ошибку:

Поэтому не рекомендуется использовать Material theme: она не будет работать так, как нужно. И не смешивайте Jetpack Composable и Glance, чтобы избежать конфликта имён. Где-то у вас зарендерится Glance, где-то неподдерживаемый контрол, но в итоге весь виджет не отрисуется.
Что ещё интересного: Glance поддерживает довольно простую схему добавления действия по нажатию на элемент.
class SomeWidget : GlanceAppWidget() < @Composable override fun Content() < Column ( modifier = GlanceModifier.fillMaxSize().Clickable(actionRunCallback()), ) class SomeAction : ActionCallback < override suspend fun onAction( context: Context, glanceId: Glanceld, parameters: ActionParameters ) < /**Магия*/ >>
Никаких pending intent теперь нет. Теперь мы можем добавить в любой Composable через Modifier специальный clickable для поддержки действия, определяемого actionRunCallback, в который передается ссылка на id нажимаемого элемента виджета и параметры действия для конфигурации.
Подобный механизм есть для логики перехода на какую-то activity или запуска сервиса. Для этого мы можем использовать специальные команды, которые нам доступны в Glance — actionStartActivity, actionStartService.
Как теперь обновлять и хранить состояние
Теперь давайте посмотрим на самое интересное. У нас виджеты stateless, но интерактивные. Мы можем обновлять их, заполняя данными, рендерить их. Как же мы это будем правильно делать, и как это состояние, по сути, мы можем хранить?
Если мы говорим про обычный Compose, там есть такая классная штука, как remember, которая позволяет отследить mutable state какой-то переменной.
@Composable fun ClockText() < val currentTimeMillis = remember < mutableState0f (System.currentTimeMillis()) >LaunchedEffect(key1 = currentTimeMillis) < while (true) < delay(250) currentTimeMillis.value = System.currentTimeMillis() >> Box() < Text( text = DateUtils.formatDateTime(LocalContext.current, currentTimeMillis.value, DateUtils.FORMAT_SHOW_TIME), /**…*/ ) >>
Например, если мы делаем часики, то создаём переменную, которая завязана просто на изменение текущего времени, и с помощи того же самого LaunchedEffect в цикле можем для конкретного ключа отслеживать изменения этой переменной, обновлять её. Например, если мы завязали, что текст у нас будет зависеть от этой переменной, то она будет автоматически его обновлять.
Но с Glance такого не получится, remember с ним не работает. И мы в итоге увидим опять же ошибку нашего виджета и пугающий output.
Чтобы решить проблему обновления состояния, у нас есть специальный GlanceStateDefiniton, завязанный на Preferences.
Под капотом это те же Preferences DataStore, которые могут использоваться не только для того, чтобы автоматически представлять настройки приложению, но и расшариваться с другими виджетами. Для работы с ними потребуется создать свой кастомный GlanceStateDefiniton на Preferences и задать путь, куда у нас будут сохраняться данные, и откуда считываться.
object CustomGlanceStateDefinition : GlanceStateDefinition < override suspend fun getDataStore(context: Context, fileKey: String): DataStore < return context.dataStore >override fun getLocation(context: Context, fileKey: String): File < return File(context.applicationContext.filesDir, "datastore/$fileName") >private const val fileName = "widget_store" private val Context.dataStore: DataStore by preferencesDataStore(name = fileName) >
И у нас получается такая схема: мы что-то в нашей бизнес-логике сохраняем в эти Preferences, затем в ресивере нашего виджета инициируем обновление с помощью UpdateAppWidgetState, указывая всю нужную информацию для отрисовки виджета.
То есть для каждого виджета мы должны указать свой stateDefinition на основе GlanceStateDefinition, и в методе content мы можем воспользоваться currentState, это как раз и есть переменная состояния текущего виджета. currentState позволит нам получить доступ к тем или иным Preferences, на которые завязан наш виджет, взять данные по некоторому ключу и положить их в наш composable.
Если мы хотим добавить серьёзную бизнес-логику, то в принципе мы можем использовать тот же архитектурный подход и инструментарий, что в прежнем решении, но в более современной версии. То есть мы можем:
- Использовать CoroutineScope для многопоточности
- Добавить какой-нибудь хороший паттерн с UseCase, чтобы всё не запускалось в нашем виджете. Конечно, мы можем в ресивере оставлять запуск нашего скоупа корутин, сразу дёргать логику. Но красиво и правильно будет вынести её в юзкейс и корректно вызывать по триггеру
- Также для шедулирования отложенных запросов можем использовать WorkManager API как замену фоновым сервисам
Ну и, разумеется, наш классический BroadcastReceiver, который позволяет нам отслеживать те или иные события, оповещать о них и реагировать.

В целом у нас получается такая архитектура, что мы берём наш AppWidgetReceiver, в нем в каком-то методе вызываем UseCase, который стучится, например, в репозиторий или вызывает другую логику. Затем мы в UseCase после получения ответа кладём некоторые данные в Preferences, после чего вызываем обновление нашего виджета в AppWidgetReceiver, в самом виджете считываем значения из Preferences и при необходимости что-то туда сохраняем для дальнейшей работы.
Выглядеть это будет примерно так:
//GlanceAppWidgetReceiver coroutineScope.launch < getDateFlowUseCase.execute().collect < date ->GlanceAppWidgetManager(context) .getGlancelIds(TimeWidget::class.java) .forEach < glanceId ->/**Обновление*/ > > >
Эту логику вызываем в onReceive, нашем ресивере. Сам же ресивер мы регистрируем в переопределении метода onEnabled виджета. Итак, после получения новых данных мы проходимся по всем айдишникам наших виджетов, и затем уже для всех, либо для специального id вызываем updateAppWidgetState:
GlanceAppWidgetManager(context) .getGlanceIds(TimeWidget:: class. java) .forEach < glanceId ->updateAppWidgetState( context, PreferencesGlanceStateDefinition, glanceld ) < preferences ->preferences.toMutablePreferences().apply < this[timeKey] = date.time >> glanceAppWidget.update(context, glanceld) >
Как и в другом случае, автоматического триггера не будет, нужно вызывать вручную. . Поэтому мы сохраняем новое значение в preferences в нашей бизнес-логике. После чего вызываем обновление виджета, далее уже сам виджет считывает из preferences новые данные и перерисовывает View.
То есть мы делаем следующее:
- Заменяем remember() на PreferenceGlanceState
- Оборачиваем запросы в UseCase (GlanceAppWidgetReceiver), чтобы очистить наш код
- Затем обновляем состояние виджетов: updateAppWidgetState
Теперь посмотрим, что у нас с интервалами обновления данных.
Самыми популярными виджетами всегда были календари и часы. Данные в них требуется запрашивать по факту или после изменения.
Для корректного обновления по времени нам потребуется добавить ещё один ресивер, который будет как раз реагировать на те или иные события.
class TimeWidgetReceiver : GlanceAppWidgetReceiver() < @Inject lateinit var timeTickReceiverManager: TimeTickReceiverManager override fun onEnabled(context: Context) < super.onEnabled(context) observeData(context) timeTickReceiverManager. registerReceiver() >override fun onDisabled(context: Context) < super.onDisabled(context) timeTickReceiverManager.unregisterReceiver() coroutineScope.cancel() >>
Зарегистрируем мы его также в самом AppWidgetReceiver.
Например, для часов нам потребуется time tick. Это даст нам возможность отслеживать время каждую минуту, к сожалению, не чаще. С учетом того, сколько всего уже было оптимизировано в Android, и насколько обрезалось API виджетов, скажем спасибо и на том, что хотя бы с такой частотой оно у нас будет работать.
class TimeTickReceiverManager @Inject constructor( @ApplicationContext private val context: Context, private val updateDateUseCase: UpdateDateUseCase ) < private val broadcastReceiver = object : BroadcastReceiver() < override fun onReceive(context: Context, intent: Intent?) < if (intent?.action == Intent.ACTION_TIME_TICK) < updateDateUseCase.execute() >> fun registerReceiver()***/> fun unregisterReceiver()***/> >
И затем уже, когда в самом ресивере получаем флаг, что у нас произошло то или иное событие, мы вызываем наш UseCase, вызываем логику, получаем результат, затем вызываем обновление нашего виджета.
Также возможен и другой кейс, такой как периодический опрос для всего нашего контента. Например, у нас есть список дел, нам не нужно обновлять его каждую минуту, достаточно это делать раз в несколько часов, либо по запросу. Или же данные о погоде. Если мы будем слушать какой-то сервис нон-стоп, у нас просто сядет батарея и наше приложение попадёт в плохой priority bucket в плане поддержания фоновой работы и разрешений.

В таком случае мы используем WorkManager API для шедулирования. Можно его использовать прямо из ресивера, либо использовать некий промежуточный менеджер, а в самом WorkManager будут создаваться периодические запросы на вызов того же самого UseCase.
Например, если у нас виджет погоды, то в WorkManager мы создаём соответствующий запрос, обязательно указываем все наши констрейнты, интервал обновления, и затем уже в этом реквесте мы вызываем задачу воркера, под капотом которой обращаемся к UseCase (если, конечно, используем его).
class WeatherReadManager @Inject constructor( private val workManager: WorkManager ) < fun execute() = enqueueWorker( private fun enqueueWorker() < workManager.enqueueUniquePeriodicWork( WeatherReadTask.TAG, ExistingPeriodicWorkPolicy.KEEP, buildRequest() ) >private fun buildRequest(): PeriodicWorkRequest < return PeriodicWorkRequestBuilder(1, TimeUnit.HOURS) .addTag(WeatherReadTask.TAG) .setConstraints(updateConstraints) .build() > ) >
Если же нам необходимо обновить принудительно, можно воспользоваться триггером-кнопкой, по которой прямо вызвать выполнение UseCase.
Ну и, разумеется, чтобы всё правильно триггерилось, необходимо запускать те же самые события через BroadcastReceiver. Например, если нам важно отслеживать местоположение, то нам достаточно делать это по факту, а не дёргать запросы каждые 5 секунд. Просто добавляем ресиверы на отслеживание событий и запускаем нашу логику, когда получаем уведомление.
А если нам нужно вывести то, что не поддерживается? Например, карту-сэмпл с виджетом PeopleInSpace:

К сожалению, у меня не получилось обратиться к нему напрямую, меня отрезал VPN. Но в самом виджете, по коду, логика работает так: при изменении локации мы показываем карту, а контрол карты у нас не является разрешённым для отображения. Поэтому в этом случае мы превращаем View в Bitmap, как и раньше:
val bitmap = withContext(Dispatchers.Main) < suspendCoroutine < cont ->val mapSnapshot = MapSnapshot( < if (it.status == MapSnapshot.Status.CANVAS_OK) < val bitmap = Bitmap.createBitmap(it.bitmap) cont.resume(bitmap) >>, MapSnapshot.INCLUDE_FLAG_UPTODATE or MapSnapshot.INCLUDE_FLAG_SCALED, MapTileProviderBasic(context, source, null), listOf(stationMarker), projection ) launch(Dispatchers.IO) < mapSnapshot. run() >> >
Некоторые библиотеки сами умеют делать какие-то Bitmap-снэпшоты, либо просто снимок экрана. Например, авторы PeopleInSpace используют osmdroid, который у меня как раз из-за VPN не запустился. И в нём есть специальный MapSnapshot для создания Bitmap, и этот Bitmap мы уже потом кладём в переменную состояния и затем вызываем триггер для использования.
Резюмируем по Android-виджетам:
- Они интерактивные, но без анимации. Как и раньше, анимацию мы имитируем, но сейчас с этим сложнее.
- Поддерживают современный архитектурный подход, который нам нужно учитывать.
- Не все Composable сейчас имеют аналог в Glance, но он развивается, ситуация может измениться.
- Glance-виджеты совместимы с RemoteView
- Сложности с интервалами обновления, не гарантированный результат
Теперь посмотрим, что с iOS
Виджеты в iOS появились в 2020-м. Но на самом деле, в этом году появился новый WidgetKit — новый фреймворк для iOS 14, который позволяет просто реализовывать те же самые виджеты в концепции SwiftUI. Если говорить откровенно, то самые первые виджеты появились еще в iOS 11, но сейчас этот паттерн устарел.
Что собой представляет виджет в iOS? Это, как и в Android, не мини-приложение, а его часть для отображения на экране. Вы можете минимизировать логику — это ваше право.
В iOS 11 появились NCWidgetProvidingAPI — они позволяли реагировать на действия пользователя, хотя и имели ограниченный инструментарий. С новым фреймворком инструментарий расширили, но урезали и возможности.
Виджеты под iOS 14 поддерживают Shared код. Вы можете с помощью XCode сделать специальное Multiplatform приложение и выделить там часть кода как общую, поддерживаемую в разных таргетах. То, что поддерживается в семантике WidgetKit (то есть всё, кроме классических UIKit View), вы можете вызывать из вашего виджета. И разумеется, весь код будет на SwiftUI. Например, вот код, в котором в зависимости от размера виджета мы вызываем тот или иной View на SwiftUI, куда передает entry, то есть информацию:
struct TodoWidgetView : View < @Environment(\.widgetFamily) var family var entry: Provider.Entry var body: some View < switch family < case .systemSmall: TodoWidgetSmallView(entry: entry) case .systemLarge: TodoWidgetLargeView(entry: entry) default: Text("Not supported") >> >
Размеры доступны самые разные, вы легко можете кастомизировать UI вашего виджета под разные размеры. Но есть много ограничений: View может быть только статичным, состояний нет, интерактивных контролов и анимации тоже нет.
Сам виджет состоит из специального статического View Content, который меняется в зависимости от TimelineProvider, который оперирует Snapshot’ами в виде цепочки Timeline entry. То есть вы создаете массив состояний виджета, который через заданные промежутки времени отрисовываются в вашем View. Сам Snapshot подразумевает, что все статичное. Что подсказывает нам, что мы можем создать эффект динамики из цепочки отрисовываемых с определенной скоростью Timeline entry.

Timeline провайдер работает так: при старте виджета в Timeline загружается определенная последовательность entry с заданными промежутками времени и политиками обновления виджета. Например, если задать atEnd, то после конца обновления текущей стадии виджета берется следующий слепок через определенный промежуток времени и отображается. Чем больше промежуток, тем точнее выполнение, потому что система может регулировать старт того или иного события. Когда все Timeline закончились, вызывается последний Timeline, говорящий, что больше обновлять нечего, и виджет выходит из круга обновлений.
У вас есть возможность передать данные с основным приложением через Deeplink, в который вы можете обернуть View.
struct SimpleWidgetEntryView: View < var entry: SimpleProvider.Entry var body: some View < Link(destination: URL(string: "widget://link1")!) < Text ("Link 1") >> > @main struct WidgetTestApp: App < var body: some Scene < WindowGroup < Text ("Test") .onOpenURL < url in print("Received deep link: \(url)") >> > >
Рассмотрим несколько кейсов.
Что с UI
Виджеты не поддерживают ничего, что связано с анимацией: ни карты, ни прокрутку, ни видео. Что самое неприятное — никакого UIView и UIViewRepresentable. Поэтому мы можем использовать либо только SwiftUI, либо, по традиции, превратить все в картинку и выводить в UIImage. То есть мы создаем расширение, которое превращает View с помощью GraphicsImageRenderer и CGContext в UIImage. А затем UIImage показывается через специальный инициализатор с помощью задание границ для Image.
Например, мы можем зарендерить карту как контрол UIKit промежуточно, снять слепок изображения и поставить в наш View.

Также мы можем использовать готовые библиотеки, например, MapView. MKMapSnapshotter будет делать скрины, которые вы положите в View.
//MKMapSnapshot from MKMapView var _snapShotOptions: MKMapSnapshotOptions var _snapShot: MKMapSnapshotter _snapShotOptions.region = mapRegion //… _snapShot.start < (snapshot, error) —>Void in if error == nil < let image = snapshot?.image //… >else < print("error") >> //…

Резюмируем: либо мы используем только SwiftUI, либо используем картинки для имитации интерактивного UI.
Интервалы обновления
Мы рассмотрели, что может быть массив entry, но массивы могут быть конечными. А если нам нужно запрашивать время каждую секунду? Конечно, это спорно в плане памяти и сразу отрубает возможность создания чего-то на UIKit. Хочется сделать оптимальнее, с помощью SwiftUI. Воспользуемся старым добрым таймером, который засунем в TimelineProvider в специальный метод getTimeline и будем вызывать раз в полсекунды наш виджет с новым Timeline, добавляя туда новый entry с указанной датой и данными.

Если мы хотим подключить сложную логику, например, получая что-то асинхронно по сети, либо из хранилища, то нам потребуется всю логику положить в Timeline Provider и в Completion-блоке вызвать Timeline с конкретными данными по факту обновления.

В современной версии мы можем использовать таски и async-await. Таски у нас не detached, поэтому мы находимся в главном потоке UI и на другом потоке вызывается только метод loadData. Мы получаем данные из сервиса, создаем на их основе entry. Затем, например, создаем refreshData и в Timeline инициализируем entry, указываем, когда будет следующее событие, и передаем в Completion-блок
func getTimeline(for configuration: RideDataIntent, in context: Context, completion: @escaping (Timeline) -> ()) < var entries: [SimpleEntry] = [] Task < let currentDate = Date() let data = await someService.loadData() entries.append(SimpleEntry(date: currentDate, data: data)) let refreshDate = Calendar.current.date(byAdding: .minutes, value: 15, to: currentDate)! let timeline = Timeline(entries: entries, policy: .after(refreshDate)) completion(timeline) >>
Асинхронными могут быть и картинки. Например, если данные содержат URL картинки, а не саму картинку. Казалось бы, все классно, но ничего подобного: API, конечно, асинхронное, но Callback для виджета нет, поэтому виджет не понимает, нужно ли ему перезагружаться, чтобы показать эту картинку. Поэтому нам потребуется собственное API для загрузки изображений и в его Completion-блоке можем, например, дернуть виджет на перерисовку. Команду WidgetCenter.shared.reloadTimelines можно дернуть в любой точке приложения, но правильнее будет не засовывать его в бизнес-логику, чтобы не нарушать целостность функциональных слоев.

В итоге мы можем получить такое изображение, которое точно покажет данные с карты. Например, если мы взяли готовый URL, который мы создали через API static на основе координат и зарендерить нашу картинку в виджете. Даже сами Apple снэпшотят свою карту и показывают в виджете, то есть это тоже не интерактив.

Подведем итоги
Чтобы на iOS-виджете корректно работали запросы и обновления, мы можем использовать либо async-await, либо completionHandler. Вся логика запросов будет в TimelineProvider. Конечно, вы выносите саму логику в какой-нибудь менеджер или релоадер, но его функцию вы дергаете в TimelineProvider и callback приходит в нем же. Также мы используем в качестве решения Timer, если нам необходимо точное время выполнения.
Суммируем по iOS-виджетам:
- Виджеты статичные, stateless и не интерактивные
- Можно использовать общий код приложения, чтобы сократить количество работы
- Используем таймеры для событий
- Архитектура решений может быть мудреной, потому что нет единого подхода, рекомендованного Apple, как работать со сложной логикой.
Сравним с Android:
- Android-виджеты тоже без анимации, но они интерактивные.
- Для них есть специальный архитектурный подход
- Не все Composable имеют аналог в Glance
- Можем использовать RemoteView, но из-за того, что не все имеет аналог в Glance, могут быть трудности
- Есть сложности с интервалами обновления, не гарантированный результат
Какая есть проблема с виджетами в целом? И на iOS, и на Android есть проблема управления состоянием и обновлений и правильная архитектура решения. Также в обеих ОС есть проблема с имитацией сложных UI. Причем если в iOS мы можем сделать анимацию за счет смены изображений раз в секунду, то на Android из-за ограничений это сделать не получится. И поэтому не все можно превратить в картинку.
Я собрала для вас список полезных источников:
- https://github.com/anioutkazharkova/todoapp
- https://proandroiddev.com/when-jetpacks-glance-met-his-fellow-worker-work-manager-18cf19eff983
- https://medium.com/androiddevelopers/demystifying-jetpack-glance-for-app-widgets-8fbc7041955c
- https://habr.com/ru/post/519146/
- https://developer.apple.com/documentation/widgetkit/building_widgets_using_widgetkit_and_swiftui
Спасибо за внимание!
Напоследок напомним, что в мае на нашей конференции Mobius Анна представит новый доклад «Упрощаем и укрощаем UI для Android с помощью аннотаций». Также там будет множество другого контента для мобильных разработчиков, мы уже публиковали на Хабре программу. Конференция пройдёт 12–13 мая (онлайн) и 19–20 мая (Москва + возможность онлайн-участия).
- виджеты
- android
- android development
- ios
- ios development
- ios разработка
- android разработка
- Блог компании JUG Ru Group
- Разработка под iOS
- Разработка под Android