30.09.2026 721 материалов

Как ИИ-агент проверяет экраны Android-приложений, не запуская само приложение

Разработчик научил ИИ-агента рендерить составные экраны Android из реальных XML-разметок с тестовыми данными — без запуска приложения, эмулятора и инфраструктуры.

Как ИИ-агент проверяет экраны Android-приложений, не запуская само приложение

Один XML-файл — это ещё не экран. Настоящий экран Android-приложения — это связка Activity, Fragment, списка с данными и всплывающих слоёв. Чтобы ИИ-агент мог визуально проверить результат своей работы над UI, пришлось научить его собирать эту связку целиком, но без запуска приложения.

Зачем ИИ-агенту рисовать экраны

Представьте, что вы — программист, и у вас есть помощник-агент на базе ИИ. Он умеет менять XML-разметки Android-приложения и должен показать вам, что получилось на экране. Раньше такой агент уже мог отрендерить одну простую разметку — взять XML-файл, «inflate» его (то есть развернуть в визуальные элементы) через фреймворк Robolectric и вернуть картинку с деревом виджетов. Этого хватало для кнопки или карточки.

Но когда речь зашла о реальном экране рабочего приложения, одного файла стало мало. Типичный экран в Android — это не один файл, а несколько слоёв: Activity (контейнер верхнего уровня), внутри него Fragment (отдельный экран или его часть), RecyclerView (прокручиваемый список элементов) и порой оверлей загрузки поверх всего. Запустить всё это в обычном режиме — значит потянуть за собой внедрение зависимостей, навигацию, сетевые запросы и кучу другого кода. Для визуальной проверки это избыточно и медленно.

Компромисс: реальные ресурсы, но виртуальное выполнение

Решение, которое предлагает проект android-ui-renderer-mcp от разработчика с ником hram, строится на одном ключевом принципе: визуальные ресурсы берутся из настоящего приложения, а вот состояние и данные задаются явно в запросе.

Что это значит на практике:

  • XML-разметки Activity, Fragment и элементов списка — настоящие, из вашего проекта
  • Drawable-ресурсы (иконки, фоны, бейджи) — тоже реальные
  • А вот код Fragment, система внедрения зависимостей, навигация и сетевой слой — не запускаются вообще
  • Данные для списка, состояние загрузки, выбранная книга — задаются в JSON-запросе вручную

Если говорить бытовым языком: это как собрать макет комнаты из настоящей мебели, но не включать электричество и воду. Вы видите, как всё выглядит, но не тратите время на подключение коммуникаций.

Как собирается экран из кусочков

Инструмент поддерживает цель рендеринга activity_fragment — она берёт XML-разметку Activity, находит в ней контейнер для фрагмента, потом inflate-ит отдельно XML-разметку Fragment и вставляет её внутрь. Класс Fragment при этом не создаётся — только визуальная структура из XML.

В запросе вы указываете, какие файлы разметки использовать, размеры экрана, плотность пикселей и ориентацию. Это важно: если ваше приложение работает на планшете с разрешением 1280×800 и плотностью 240 dpi, агент должен видеть именно эти параметры — иначе измерения элементов будут некорректными.

Список с тестовыми данными

Пустой RecyclerView — бесполезен для визуальной проверки. Но запускать настоящий адаптер с реальными данными — снова значит тянуть за собой слишком много кода. Поэтому данные для списка тоже описываются в запросе: указывается реальный XML-файл элемента списка, ориентация, количество строк и конкретное содержимое каждой строки.

Например, для строки «книга в библиотеке» можно задать заголовок, обложку, бейдж статуса и цвет выделения. Рендерер создаёт временный адаптер и inflate-ит настоящий элемент списка для каждой строки. Структура интерфейса — настоящая, данные — тестовые и полностью под контролем.

Это работает иначе, чем привычные tools:text и tools:visibility в Android Studio. Агент не угадывает значения — он явно описывает, что должно быть на экране.

Индикатор загрузки: заморозить, чтобы не мигал

Оверлей загрузки — это ещё одна XML-разметка, наложенная поверх Activity и Fragment. Тонкий момент: если в оверлее есть анимированный индикатор (ProgressBar с неопределённым прогрессом), два последовательных скриншота могут отличаться из-за разных фаз анимации. Для автоматизированной визуальной проверки это лишний источник случайности.

Решение простое: перед рендерингом индикатор «замораживается» в статичное кольцо. Это не проверяет, работает ли анимация, но подтверждает, что индикатор есть, находится в правильном месте и имеет ожидаемую геометрию. Для задач визуального контроля этого достаточно.

Повторяемость как требование

Автор проекта столкнулся с проблемой: сохранённый рендер (картинка и дерево виджетов) был на месте, а вот запрос, который его породил, — нет. Какой инструмент использовался, какие аргументы передавались, из какого модуля и варианта сборки — ничего не сохранилось. Получился воспроизводимый рендерер с невоспроизводимой историей запусков.

Теперь каждый рендер сохраняет два файла: request.json (нормализованный запрос) и replay.json (рецепт с параметрами проекта и Gradle-задачей). С этими файлами можно повторить рендеринг на той же ревизии кода и получить битово идентичную картинку. Не похожую, а именно такую же.

Что нашёл демо-проект

Любопытно, что открытый демо-проект (созданный специально для публикации изображений) обнаружил реальные проблемы, которых не было в рабочем проекте.

Во-первых, рендерер молча предполагал наличие JUnit в зависимостях. Рабочий проект, созданный по шаблону Android Studio, содержал JUnit, так что проблема не проявлялась. Демо-проект — нет, и первый рендер упал с ошибкой. Теперь JUnit 4.13.2 добавляется автоматически, если проект его не объявил.

Во-вторых, в самом демо приложении обнаружился баг контрастности в ночном теме: кнопка с обводкой была почти невидна на тёмном фоне. Рендер в ночном режиме это показал — а чтение XML нет.

В-третьих, увеличенный масштаб шрифта (fontScale: 1.3) обрезал длинный заголовок с многоточием. PNG-картинка выглядела нормально, но дерево виджетов показало, что 81 символ превратился в многоточие. Это та проблема, которую при беглом взгляде на скриншот легко пропустить.

Интересный технический нюанс: начиная с Android 14, масштабирование крупного текста стало нелинейным. 16sp при fontScale: 1.3 — это 40 пикселей, а не 41,6, как можно было бы ожидать.

Кто за что отвечал

Разработатель описывает распределение ролей между собой и ИИ-агентом. Человек выбирал, какую часть реальности инструмент должен моделировать и чего он не должен делать. Агент — изучал XML-разметку реального экрана, предлагал технические решения (например, детерминированный адаптер для RecyclerView), реализовывал их и писал демо-приложение.

Автор не набирал большую часть кода вручную. Его задача была в другом: определить границу между тем, что должно остаться реальным, и тем, что можно заменить детерминированной моделью.

Ограничения

Стоит понимать, что это не универсальный инструмент тестирования. Поддерживается только один тип цели — activity_fragment с одним контейнером и одним фрагментом. Встроенная навигация между фрагментами не моделируется. RecyclerView получает только явно заданные локальные строки — пагинация, асинхронная загрузка и реальные источники данных не учитываются. Замороженный индикатор проверяет наличие и геометрию, но не анимацию. И главное: Robolectric — это не настоящее устройство. Он приближается к реальности, но не заменяет тестирование на физическом планшете или телефоне.

Зачем это обычному пользователю

Вы, скорее всего, не будете запускать этот инструмент напрямую. Но если вы пользуетесь Android-приложениями, которые разрабатываются с участием ИИ-агентов, такие инструменты влияют на качество интерфейса. Агент, который может визуально проверить результат своих изменений в XML-разметке, реже отправит в продакшн сломанный экран с обрезанным текстом, невидимой кнопкой или перекрытым списком. И делает это быстрее, чем если бы каждый раз приходилось запускать эмулятор.

Проект android-ui-renderer-mcp выложен в открытый доступ на GitHub.