Разработчик ПО
В этом году Compose официально стал основным инструментом для создания пользовательских интерфейсов в Android, а устаревшие представления были переведены в режим обслуживания. Для Google Play этот путь начался в 2020 году. Переход на Jetpack Compose, который затронул миллиарды пользователей и потребовал почти два десятилетия работы инженеров, был осуществлен благодаря проверенным рекомендациям, тесному сотрудничеству с основной командой Compose и непрерывному развитию инструментов.
Узнайте, как модернизация Google Play с помощью Compose позволила заметно повысить скорость работы и долгосрочную стабильность. На этом занятии мы расскажем, как связать рабочие процессы проектирования Figma с реализацией Compose, автоматизировать контроль качества, чтобы предотвратить ненужные рекомпозиции, и создать выразительные интерфейсы с динамическими цветами, разнообразной типографикой и многослойными фигурами. Вы узнаете о проверенных стратегиях, которые помогут быстрее сортировать ошибки и уверенно масштабировать переход на Compose.
ComposeView непосредственно в корне Activity.MaterialTheme и библиотеки Material Color Utilities. Это позволяет избежать разбора Activity, хотя создание цветовых схем во время сборки помогает ограничить задержку во время выполнения.BoxWithConstraints.Расшифровка
Всем привет!
Меня зовут Александр.
Я представляю команду Google Play.
Это мой коллега Кирилл.
Сегодня мы расскажем, как перенесли большую часть кода из системы View и Fragment в Compose.
Давайте начнем.
Как проходило внедрение и насколько масштабным оно было?
База кода нашего приложения росла органически с 2007 года.
У нас несколько миллионов строк кода, 200 экранов и 500 уникальных компонентов интерфейса, которые используются на многих из этих экранов.
В 2020 году мы поняли, что скорость разработки, надежность и производительность не соответствуют нашим целям.
Поэтому мы решили перейти на современный набор технологий для пользовательского интерфейса.
Мы оценили множество разных библиотек.
В то время мы также общались с коллегами из AndroidX, которые рассказали нам о Compose. Мы решили внедрить этот инструмент и стали его первым крупным партнером.
Как прошло?
Шесть лет спустя 97 % показов в приложении Google Play приходится на экраны, созданные только с помощью Compose.
С 2020 года вся разработка для телефонов и больших экранов перешла на Compose.
И даже наши расширенные форм-факторы, XR, TV, Auto, Wear, перешли на Compose с 2025 года.
И Compose сыграл в этом успехе очень важную роль, поэтому мы продолжаем внедрять его все больше и больше.
У них был очень мощный и комплексный набор тестов, который помог нам внедрять новые версии Compose дважды в неделю.
Мы используем альфа-сборки и новые API, чтобы исправлять ошибки, повышать производительность и предлагать пользователям новые возможности.
Если говорить о производительности, Compose действительно обеспечивает стабильную работу нашего приложения на всех основных экранах.
Это страница результатов поиска, главная страница, страница сведений.
Производительность, задержка загрузки и задержка отрисовки – это очень важные показатели для нашего бизнеса и удобства пользователей.
Таким образом, за последние 14 месяцев мы наблюдаем улучшение на 30 %.
Но как она добилась такой известности?
Вот об этом мы и поговорим.
Давайте поговорим о плане переноса.
Поэтому мы решили действовать поэтапно.
Мы не хотели складывать все яйца в одну корзину.
Мы предприняли несколько шагов, чтобы понять риски и влияние на наш бизнес, и обрели уверенность, прежде чем полностью перейти на автоматизацию.
Я назову их так: ползание, ходьба, бег трусцой и бег.
Поэтому на первом этапе мы просто знакомились с Compose.
Мы начали со страницы с низким трафиком.
Это может быть страница настроек, лицензии третьих сторон и тому подобное.
Ваша основная цель — ознакомиться с библиотекой Compose.
Это могут быть строки, столбцы, текст, изображения и тому подобное.
А затем вам нужно начать переносить всю инфраструктуру вашего приложения и понять, как она работает в Compose.
Это могло быть связано с регистрацией показов и кликов, темами, а также с тем, что мы одновременно обновляли загрузку данных.
Так что все эти вещи.
Чтобы все экраны выглядели единообразно, мы создали модульные и функциональные тесты. Они проверяют, что объекты тем в Compose напрямую сопоставляются со стилями XML, типографикой, цветами и другими элементами.
Когда мы привыкли к этому и почувствовали себя уверенно, мы решили перейти к ходьбе.
Мы взяли небольшой компонент на экране с очень высоким трафиком и оценили, как будет выглядеть его перенос.
Мы рекомендуем взять элемент, который находится в RecyclerView, чтобы вы могли понять любые проблемы совместимости между View и системой Compose, а также увидеть производительность измерений и задержки на этом пути.
Сам компонент не обязательно должен быть очень сложным, но вам действительно нужно иметь что-то, что использует макеты, и вы знаете, текст, изображения, подобные вещи, пару разных экземпляров того, что вы обычно видите.
Вам следует обратить внимание на то, что в этой области может немного увеличиться задержка и количество задержек. Это связано с тем, что теперь вы используете два стека.
У вас есть стек рендеринга, который полностью находится в Compose, и стек, который находится в Views.
Поэтому будет происходить некоторая пробуксовка памяти, загрузка дополнительных классов и т. д.
Но по мере того, как вы будете все больше и больше внедрять Compose, это исчезнет.
Если же вам действительно нужно это сделать, вы можете немного предварительно прогреть этот экран, чтобы помочь выровнять его и снизить эти затраты в краткосрочной перспективе.
После ходьбы мы переходим к бегу трусцой.
Это когда мы уже очень, очень хорошо освоились.
Вместо того чтобы делать один компонент на странице с высоким трафиком, просто сделайте всю страницу с высоким трафиком.
Поэтому мы сохранили библиотеку навигации и использовали фрагменты, но для каждого отдельного экрана она была полностью реализована в Compose.
Мы просто заставили Fragment вернуть сам ComposeView.
Таким образом, вы можете ожидать улучшения задержки и рывков.
Вы не будете сталкиваться с проблемами с памятью и загрузкой классов, о которых я говорил.
И сам Compose также выполняет некоторые уровни кэширования на уровне ComposeView.
Поэтому, если у вас есть RecyclerView с кучей карточек, которые выглядят одинаково и могут иметь одинаковые значки, весь этот синтаксический анализ значков и использование ресурсов распределяются между всеми элементами ComposeView.
Поэтому мы перешли к бегу.
Вот когда вы действительно идете ва-банк.
Когда большая часть трафика вашего приложения будет приходиться на Compose, мы перейдем на следующий уровень.
Итак, на приведенных выше экранах у нас есть Activity.
Поэтому переместите ComposeView в корень Activity.
И большая часть вашего кода будет выглядеть относительно похоже.
Вы по-прежнему пытаетесь дать инструкцию по навигации, когда хотите сказать, что нужно перейти с одного экрана на другой.
Но вместо транзакций фрагментов вы вызываете composable-функцию.
Вы можете сделать это поверх библиотеки Nav2 или Nav3, в зависимости от того, что вы выберете.
Нам не удалось перенести все экраны.
Я уже говорил, что их много.
Поэтому мы создали совместимость с фрагментами, где у вас может быть экран на основе фрагментов, и использовали для этого оболочки AndroidFragment в Compose.
Что вы можете ожидать снова, снова, задержка и задержка важны для нашего бизнеса, поэтому мы просто продолжали продвигать этот конверт, и вы продолжаете видеть, что это улучшается, потому что теперь у нас есть кэширование на экранах и гораздо меньше этой проблемы с памятью.
но и новые возможности.
Справа вы видите пример GIF-изображения, в котором можно использовать переходы между экранами с общими элементами.
У нас есть изображение, которое увеличивается по мере перехода от одного экрана к другому, и это обеспечивает непрерывность.
Вы также можете использовать все возможности анимации Compose во время всего перехода.
А теперь я передаю слово Кириллу, который расскажет о других возможностях.
Спасибо, Александр.
Я хочу немного поговорить о теме, которая мне близка и дорога, а именно о темах и стилях для приложений Android, чем я занимаюсь уже некоторое время.
В мире View был код Java, а чуть позже код Kotlin, а затем были ресурсы X-- XML, такие как цвета, строки, размеры, макеты, темы и стили.
Все эти XML-ресурсы компилировались в таблицу ресурсов, которая включалась в APK-файл и была практически статической.
Существует API Resources.Theme.applyStyle, который можно использовать динамически во время выполнения, чтобы применять собственный стиль поверх иерархии пользовательского интерфейса.
Основное ограничение заключалось в том, что это нужно было делать до использования метода inflate контента. То есть, если вы хотели динамически применить стиль к существующему элементу пользовательского интерфейса, вам приходилось удалять и заново создавать все Activity, что было не очень удобно для пользователя.
Кроме того, поскольку применялись предварительно скомпилированные блоки XML, было сложно комбинировать несколько условных динамических оверлеев друг с другом на основе флагов эксперимента или других бизнес-требований.
Было довольно сложно построить правильную ментальную модель иерархии стилей в этом мире XML.
И наконец, в Android Studio или любом другом инструменте разработки не было единого унифицированного представления, которое позволяло бы преодолеть разрыв между миром Java, миром кода и миром XML.
И было довольно сложно жонглировать и строить правильную ментальную модель и отлаживать, почему мой стиль не применяется, почему я получаю неправильный цвет и так далее.
В новом мире, в мире Compose, в частности для системы Material Design и библиотеки Compose Material, а также для других систем проектирования, у нас есть MaterialTheme Composable, который вы применяете к самому верху иерархии пользовательского интерфейса на экране, и вы также можете применить эту MaterialTheme в качестве наложения на любую меньшую часть вашего пользовательского интерфейса.
Если вы посмотрите на экран справа, то увидите вверху карточку Play Points Gold с индикатором прогресса и текстом.
Мы применяем этот оверлей тематического оформления Material Design с пользовательской цветовой схемой, созданной библиотекой Material Color Utilities, которая доступна на GitHub.
Мы передаем этот коричневато-желтый, золотой цвет в качестве начального значения, и он генерирует полный спектр токенов цвета материала, первичный на первичном, вторичный на вторичном, которые мы затем используем внутри реализации этой карты.
Это дает разработчикам Android полный доступ к возможностям динамических тем.
Все делается в коде, все делается в Kotlin, а это значит, что гораздо проще построить правильную ментальную модель того, как все это работает, и гораздо проще отлаживать.
А внутри эти наложения прикрепляются как локальные композиции к конкретным узлам пользовательского интерфейса в вашей иерархии, где вы решите их применить.
Рекомендации по производительности: создание полной цветовой схемы с помощью библиотеки Material Color Utilities не является бесплатным.
Это связано с определенными накладными расходами.
Если у вас есть что-то достаточно сложное, требующее нескольких таких элементов, например бенто-бокс или что-то ещё, в зависимости от потребностей вашего продукта, и у вас есть несколько цветовых схем, которые вы генерируете во время выполнения, это начинает складываться, это начинает увеличивать задержку и рывки.
Итак, во-первых, вы хотите начать измерять, как это влияет на вашу производительность, и, во-вторых, вы можете перенести часть этих накладных расходов со времени выполнения на время сборки.
Вы можете использовать ту же библиотеку Material Color Utilities во время сборки, чтобы предварительно сгенерировать эти цветовые схемы в соответствии с потребностями вашего бренда или продукта и перенести эти накладные расходы на время сборки.
Локальные переменные, которые отслеживают, где именно в дереве иерархии интерфейса находятся оверлеи, гораздо дешевле, но все же не бесплатны.
Так что, в зависимости от того, сколько вы используете, десятки, сотни, это начинает складываться.
И в какой-то момент вы захотите начать смотреть на стоимость всех этих комбинированных композиционных локалей.
Это пример того, о чем я говорил, – настоящие возможности динамического оформления в Compose.
Здесь у нас четыре квадранта, одинаковые визуальные эффекты, одинаковые, назовем это карточкой события.
У нас есть главное изображение, которое занимает всю карточку, а также ярлык в левом верхнем углу, кнопка в правом нижнем углу и несколько текстов.
А справа от каждого из квадрантов вы видите выноску цветовых токенов, которые используются в качестве спецификации дизайна для каждого из элементов.
Реализация карточек для всех четырех типов одинакова.
Единственное отличие заключается в том, что оверлей тематического оформления Material Design, в который заключена карточка, генерируется или настраивается с помощью динамически создаваемой цветовой схемы на основе доминирующего цвета изображения: синего, фиолетового, желтого и красного.
Все остальные условия остаются прежними.
Вам не нужно удалять и заново создавать действие.
Вы можете применять его условно на основе параметров эксперимента или других бизнес-требований.
И это гораздо более динамичный, как я уже сказал, это гораздо более динамичный, по-настоящему динамичный мир применения цветовых настроек к вашему приложению.
А теперь я передаю слово Джону.
Да.
Это ещё один пример гибкости, о которой говорил Кирилл.
Давайте поговорим о надежности и скорости.
Поэтому все разработчики и компании стремятся быстро создавать высококачественный код.
Давайте посмотрим, как Compose помог Google Play в этом.
Итак, прежде всего, я знаю большую диаграмму, прежде всего простота архитектуры имеет огромные преимущества для всех авторов и сопровождающих кода, как людей, так и агентов.
Не буду вдаваться в подробности, но одна из ментальных моделей, которую пытается внедрить Compose, связана с однонаправленным потоком данных, и это действительно оказало большое влияние на нашу команду.
Для тех, кто не знаком с принципом работы, основы заключаются в том, что у вас есть источник данных.
Источник данных предоставляет данные.
Эти данные могут быть преобразованы в определенный интерфейс, который мы выводим в виде модели интерфейса.
И затем Compose может легко наблюдать за этим, просто вызывая свойство value для этого состояния.
Это своего рода стрелки вдоль верхней стороны этой диаграммы.
а затем события передаются в обратном направлении.
Если пользователь нажмет кнопку, это действие будет передано в преобразователь интерфейса, который обновит источник данных, выдаст новое значение и запустит преобразования. Так и работает этот цикл.
Так что все проходит через этот относительно простой цикл.
Основное преимущество, которое мы заметили, – это сокращение времени на обучение.
Больше не нужно думать, где происходят все эти вызовы – в основном потоке или в других.
Она следует одному общему циклу.
Compose делает наблюдение за данными очень простым, а данные имеют строгие гарантии согласованности между архитектурными уровнями.
Ментальная модель сократила количество разговоров об источниках истины в нашей команде и полностью устранила несколько типов состояний гонки.
Ещё одно огромное преимущество по сравнению с нашей предыдущей архитектурой "модель-представление-контроллер" заключается в том, что стало очень просто выполнять параллельное выполнение источников данных и преобразований пользовательского интерфейса.
Это позволило нам сократить задержку загрузки в магазине на десятки миллисекунд.
Это было впечатляюще.
В сочетании с однонаправленным потоком данных, другим огромным преимуществом Compose является декларативный пользовательский интерфейс.
Кирилл уже немного затронул эту тему.
Разработчикам Google Play это тоже очень понравилось.
Управление пользовательским интерфейсом теперь осуществляется в одном файле.
Справа приведен довольно простой пример.
@Composable
fun PurchaseRow(onClick: () -> Unit, modifier: Modifier) {
Row(modifier) {
Text("20% off sale")
if (LocalExpts.current.useButtonV2) {
ButtonV2("Buy", onClick = onClick)
} else {
ButtonV1("Buy", onClick = onClick)
}
}
}Мы меняем две реализации кнопки на основе флага эксперимента.
Вместо того, чтобы читать несколько файлов представлений, фрагментов и XML-файлов, вы просто читаете его сверху вниз, и это значительно упрощает задачу.
Стоимость вложенных composable-функций также намного ниже, чем вложенных иерархий View. Это позволило нам естественным образом упорядочить код, чтобы сделать его более читабельным, вместо того чтобы выжимать каждую мелочь для повышения производительности.
Ещё одно преимущество – обратные вызовы жизненного цикла стали намного проще, и их количество сократилось.
Поэтому вместо жизненного цикла представления, фрагмента и активности вы получаете одноразовый эффект, побочный эффект, и в зависимости от того, где вы его разместите, он действует очень последовательно.
Это очень удобно.
Итак, эти два архитектурных изменения действительно заложили основу для LLM, на мой взгляд, и позволили им генерировать код довольно высокого качества.
И я уверен, что многие из вас, вероятно, пробовали это или делали это со своей любимой LLM.
Вы можете сделать скриншот с аннотациями, цветами и типографикой, загрузить его и посмотреть, как он может генерировать строки, столбцы, текст и кнопки, и он делает это достаточно хорошо.
Однако у него есть свои ограничения.
И Google Play не понимает вашу базу кода, верно?
Она не знает, какие компоненты вы уже создали и можете использовать повторно, и не знает названий токенов дизайна.
Она просто предоставляет вам заданные в коде цвета и строки.
Какой контент должен быть с устройства, какой контент должен поступать с сервера или как мне убедиться, что он эффективен при использовании LazyList, а не просто создает огромный столбец?
Чтобы решить эту проблему, мы добавили к скриншотам метаданные.
названия элементов интерфейса, граничные рамки, чтобы знать, где начинаются и заканчиваются компоненты, токены дизайна для семантических названий цветов и типографики, навыки, а также некоторые подсказки, указывающие, где это будет размещено, чтобы можно было учесть эти соображения производительности.
а также сигнатуру функции высокого уровня, чтобы вы могли указать, какой контент является динамическим, а какой – статическим.
В итоге у вас получится что-то вроде того, что у нас было раньше, переходящее в то, что будет после.
Я не добавил сюда граничные рамки, потому что они бы загромождали диаграмму.
Но вы можете видеть, что эти жестко закодированные цвета и названия типографики заменяются на title large и on surface.
Названия компонентов, например заголовок карточки и значок, четко обозначены.
Таким образом, их можно найти в вашей базе кода.
И для нас, у нас есть, вы знаете, многие из них также поступают из общих библиотек дизайна, о которых Кирилл расскажет через минуту.
Поэтому вы не будете постоянно добавлять ко всем этим товарам пользовательские метки.
Вы связываете это с сигнатурой метода, как у нас внизу, OfferCard, и тогда он знает, что заголовок, endDate, изображение – все это динамическое.
Не следует задавать их в коде.
И вместе с этими семантическими именами мы теперь можем переключаться на все эти различные цветовые приложения, о которых говорил Кирилл.
И благодаря этому мы генерируем гораздо больше кода.
А теперь я передаю слово Кириллу, который расскажет о том, как мы поддерживаем качество.
Справа.
Спасибо, Александр.
Итак, продолжая тему разработки с помощью LLM и Agent Assist, мы сначала начнем со стороны UX-дизайна.
Вам нужно, чтобы в выбранном вами инструменте проектирования, будь то Figma или что-то другое, был общий шаблон, который используют все дизайнеры и в котором есть токены цвета, типографики и формы из выбранной вами системы проектирования.
Может, это Material.
Возможно, это ваша собственная система проектирования.
И вы хотите, чтобы эти шаблоны имели встроенную поддержку переключения между светлым и темным режимами, а также между режимами нормальной, средней и высокой контрастности, которые были представлены на уровне Android несколько лет назад.
Чтобы дизайнеры могли быстро переключаться между этими различными комбинациями и видеть, как на это реагируют их спецификации дизайна.
Затем, когда он дорабатывается и передаётся на сторону реализации, в этом конвейере экспорта вы хотите иметь какую-то возможность отслеживать, что каждый цвет, каждый элемент типографики, каждая фигура использует соответствующий токен, а не является жёстко закодированным значением RGB, или жёстко закодированным семейством шрифтов, или размером шрифта.
И наконец, мы обнаружили, что в Google Play очень полезно использовать не десятки, а сотни компонентов, кластеров, карточек и контейнеров, которые применяются на разных экранах.
Мы поняли, что удобно хранить всю эту информацию в одном месте, которое мы называем каталогом компонентов, и обучать на нем специального агента.
И это очень полезно, когда вы начинаете работу над новым экраном, новым путем пользователя или новым продуктом. Вы можете загрузить в агент первые наброски дизайна, например слайды или спецификацию Figma, и он быстро определит, какие компоненты уже существуют, чтобы их не нужно было заново проектировать и реализовывать с нуля.
И это значительно сокращает затраты на проектирование и затраты на реализацию.
Что касается реализации, мы в значительной степени полагаемся на основные и настраиваемые правила Lint.
Я хочу выделить два основных правила Lint. Первое – FrequentlyChangingValueDetector. Оно помогает выявлять источники ненужных рекомпозиций, которые могут приводить к задержкам и зависаниям. Второе – ScaffoldPaddingDetector. Оно позволяет отмечать случаи, когда мы используем composable-функцию Scaffold из Material Compose, но игнорируем входящие отступы.
Это не источник задержек и рывков, но в некоторых случаях это может привести к очень сложным для отладки проблемам, например к смещению макета в определенных случаях, когда он находится от края до края или складывается, раскладывается.
И, наконец, BoxWithConstraints, он имеет определенные соображения производительности.
Для правильного размещения всех элементов может потребоваться два кадра.
Мы не блокируем его полностью, но помечаем как предупреждение Lint, чтобы разработчик, работающий над этим экраном, знал, что он делает конкретный выбор в пользу BoxWithConstraints, или, возможно, он мог бы использовать что-то другое для этого конкретного макета.
Для автоматизации тем, опять же, токены цвета, токены типографики, токены формы.
У нас есть правило Lint, которое блокирует использование жестко закодированных цветов и типографики.
У нас есть правило Lint, которое помечает использование компонентов Material 2.
Мы хотим использовать только Material 3, самую последнюю и лучшую версию, включая новые выразительные компоненты, которые были представлены в прошлом и этом году.
У нас есть специальное правило Lint, которое запрещает считывать цвета, например, из устаревших XML-файлов.
Мы хотим использовать Compose в первую очередь и только Compose для новых экранов.
А у текстовых composable-функций, особенно в системе проектирования Material Design, есть резервные варианты стиля и цвета.
Они считывают его из цвета локального контента и локальной типографики, но мы не хотим на это полагаться.
Мы хотим, чтобы каждый текст намеренно использовал определенный стиль, стиль типографики и определенный цвет из спецификации дизайна.
Это правило Lint, которое у нас есть.
И последнее, по аналогии со ScaffoldPaddingDetector, у нас есть специальное правило Lint, которое гарантирует, что каждая функция Composable, которая у нас есть, использует свой модификатор ввода.
Опять же, чтобы предотвратить, например, неправильное размещение новых объектов или несоответствие фону и так далее.
Это три основных момента, которые мы хотели бы, чтобы вы вынесли из этой презентации.
Во-первых, сегодня, в конце 2026 года, Compose – это, по нашему мнению, зрелый набор инструментов для создания пользовательского интерфейса, проверенный в масштабе.
Android сам по себе ориентирован на Compose.
Ранее в этом году на Google I/O мы объявили, что все библиотеки пользовательского интерфейса и все агентские потоки ориентированы на Compose.
И, как Джон упоминал ранее в презентации, ползайте, ходите, бегайте трусцой и бегайте, мы предлагаем вам мигрировать постепенно, особенно большие кодовые базы, чтобы ускорить вашу скорость для потоков разработчиков и для агентских потоков.
Спасибо за внимание. Через несколько минут мы с Джоном выйдем к вам и ответим на ваши вопросы.
Ещё раз спасибо за ваше время.