Как инженеры Instagram Direct создали архитектуру пользовательского интерфейса, основанную на искусственном интеллекте, с помощью Jetpack Compose и снизили стоимость токенов на сессию агента на 33%.
11 минут чтения
Эта статья написана в сотрудничестве с командой Meta.
Instagram Direct — одна из ключевых платформ Instagram, обрабатывающая миллиарды сообщений пользователей каждый день. За годы работы команда выжала максимум микрооптимизации из устаревшей системы Android View. Однако поддержание и расширение сильно оптимизированной устаревшей платформы создает значительный технический долг и накладные расходы на разработку, особенно по мере того, как команды все чаще используют декларативный пользовательский интерфейс и помощников по программированию на основе искусственного интеллекта.
Внедрение Jetpack Compose для Instagram Direct вышло за рамки обычной модернизации пользовательского интерфейса. Команда разработала кодовую базу, изначально предназначенную для ИИ, которая на 50% меньше оригинальной реализации, при этом добившись сокращения времени выполнения ИИ-агента на 35% , уменьшения количества обменов между инженером и агентом на 32% и снижения стоимости токенов на 33% . В тесном сотрудничестве с Google команда внедрила Jetpack Compose, сохранив при этом высокий уровень производительности. Благодаря оптимизации производительности Meta и Google улучшили Compose не только для Instagram, но и для всей экосистемы разработчиков Android.
Модернизация кодовой базы в масштабах, охватывающих огромные объемы.
Искусственный интеллект быстро стал повседневным помощником инженеров в отрасли, и его применение к крупномасштабной кодовой базе, такой как Instagram, уже приносит реальный прирост производительности. Команда Instagram Direct поставила перед собой более амбициозную цель. Вместо того чтобы просто направлять инструменты ИИ на существующий код, команда перепроектировала кодовую базу и ее архитектуру, сделав их изначально ориентированными на ИИ, многократно увеличив влияние ИИ по сравнению с тем, что может дать простое обновление кода.
Команда Instagram Direct выбрала Jetpack Compose в качестве ключевого компонента для построения архитектуры пользовательского интерфейса, ориентированной на ИИ. Его декларативный характер гарантирует лаконичность, предсказуемость и структурную простоту кода для анализа моделями ИИ, с меньшим количеством побочных эффектов, менее выраженным неявным состоянием и более четкими границами компонентов.
Переход на Jetpack Compose потребовал тщательного планирования. Сотни миллионов людей ежедневно отправляют сообщения в Instagram, поэтому переход должен был быть постепенным, плавным и не нарушать пользовательский опыт, пока команда перестраивала базовую архитектуру. Чтобы проиллюстрировать масштаб задачи: отдельные компоненты пользовательского интерфейса могут отображаться в более чем 160 различных вариантах состояния , а один экран диалога обрабатывает более 200 различных типов сообщений .

При миграции кода такого размера на Compose возникает соблазн пойти по легкому пути и встроить компоненты пользовательского интерфейса Compose в существующую иерархию представлений. В качестве поэтапного шага в ходе постепенной миграции это вполне допустимо. Однако в долгосрочной перспективе интеграция Compose в кодовую базу, основанную на представлениях, представляет собой проблему. Инструменты искусственного интеллекта часто выбирают путь наименьшего сопротивления. Если смешивать декларативный и императивный код пользовательского интерфейса, ИИ, скорее всего, будет смешивать их неправильно, что приведет к появлению скрытых ошибок, техническому долгу и снижению производительности.
Создание архитектуры пользовательского интерфейса, изначально предназначенной для искусственного интеллекта.
В масштабах Instagram определенная степень архитектурной абстракции неизбежна, и именно она обеспечивает удобство поддержки приложения по мере его роста. Рассмотрим распространенный шаблон, где каждый тип элемента RecyclerView моделируется как потомок пользовательского базового класса RecyclerViewItem , который предоставляет обычные механизмы жизненного цикла, такие как onBind .
Пример 1
class ChatItem( val features: FeatureFlagProvider ) : RecyclerViewItem<ComposeViewHolder, ChatUiState> { // Imperative context: // AI could often take the path of least resistance and generate a mutable // state here, dispatched outside the ChatUiState. This class survives // re-bindings and is shared across multiple items, ultimately leading to // unexpected, hard-to-reproduce bugs. var isPinned: Boolean = false override fun onBind(holder: ComposeViewHolder, uiState: ChatUiState) { // Imperative context val isPinnedChatsEnabled = features.isEnabled("pinned_chats_feature") // Declarative context holder.composeView.setContent { // Blending imperative and declarative contexts if (isPinnedChatsEnabled) { Button(onClick = { isPinned = !isPinned }) { Text(if (isPinned) "Unpin" else "Pin") } } ... } } }
В приведенном выше фрагменте кода возникают две проблемы. Во-первых, флаг isPinnedChatsEnabled считывается в императивном коде, а затем захватывается внутри лямбда-функции Compose, что создает тонкую взаимосвязь между парадигмами. Во-вторых, isPinned существует как изменяемое поле самого элемента, а не в ChatUiState , поэтому оно сохраняется при повторной привязке и повторном использовании RecyclerView между строками, вызывая утечки и ошибки, которые трудно воспроизвести.
Даже если код оптимизирован за счет добавления к элементу отдельной функции с аннотацией @Composable , проблемы остаются.
Пример 2
class ChatItem( val features: FeatureFlagProvider ) : ComposeRecyclerViewItem<ChatUiState> { // Imperative context val isPinnedChatsEnabled = features.isEnabled("pinned_chats_feature") var isPinned: Boolean = false // Declarative context @Composable override fun Content(uiState: ChatUiState) { // Blending imperative and declarative contexts if (isPinnedChatsEnabled) { Button(onClick = { isPinned = !isPinned }) { Text(if (isPinned) "Unpin" else "Pin") } } ... } }
Это намеренно простой пример, но он иллюстрирует более широкую проблему: чем меньше ограничений накладывается на ИИ, тем ниже качество кода, который он производит со временем. Защитные механизмы и навыки помогают, но сами по себе они недостаточны, потому что, когда ИИ сталкивается с препятствиями, он часто обходит их, чтобы устранить блокировку.
Чтобы сделать кодовую базу удобной для использования с ИИ, необходимо следовать двум практическим правилам:
- Сведите к минимуму зависимость от пользовательского контекста. Чем больше специфических, зависящих от кода знаний требуется агенту ИИ для внесения корректных изменений, тем ниже качество его результата. Чем ближе кодовая база к известным передовым практикам, тем лучше результаты работы ИИ.
- Кодовая база, ориентированная на ИИ, должна устанавливать собственные границы. Заполнение пробелов в проектировании навыками ИИ не масштабируется, поскольку каждый навык, загруженный в контекст, стоит токенов и может ухудшить производительность агента. Вместо этого, эту нагрузку должна нести сама архитектура. Агенты ИИ, естественно, выбирают путь наименьшего сопротивления, поэтому проектирование должно обеспечивать этот путь к корректному и высококачественному коду, в то время как ошибочные проектные решения должно быть трудно и дорого реализовать.
Элемент списка по-прежнему может быть представлен собственной абстракцией, но в этом случае весь код Compose находится в конструкторе, поэтому он не имеет доступа к членам класса или состоянию, и единственным источником аргументов является конструктор. Это делает его эквивалентным обычной функции с аннотацией @Composable , при этом он соответствует существующей архитектуре.
Пример 3
class ChatItem( val features: FeatureFlagProvider, val onPin: (Boolean) -> Unit, ) : ComposeItem<ChatUiState>( // Compose UI content = { uiState: ChatUiState -> val isPinnedChatsEnabled = features.isEnabled("pinned_chats_feature") if (isPinnedChatsEnabled) { Button(onClick = { onPin(!uiState.isPinned) }) { Text(if (uiState.isPinned) "Unpin" else "Pin") } } ... }, )
Миграция кодовой базы такого размера — это масштабная задача. Долгое время сотни компонентов пользовательского интерфейса, составляющих большую часть Direct UI, должны были сосуществовать со своими устаревшими аналогами, при этом оба компонента поддерживались параллельно. Рабочие процессы на основе искусственного интеллекта помогли сделать эту параллельную миграцию возможной, ускорив процесс написания огромных объемов кода. Именно такой подход позволил команде Direct выполнить миграцию в рекордно короткие сроки, не нарушая работу остальной команды, которая продолжала выпускать функции, улучшающие пользовательский опыт миллионов людей каждый день.
Несколько инженеров запускали собственных агентов ИИ, используя общую базу знаний, состоящую из многократно используемых навыков и соглашений, разработанных в процессе миграции. Это позволило синхронизировать рабочие процессы и лучшие практики внутри команды, избавив каждого инженера от необходимости заново их изучать. В рамках каждой области команда выполняла миграцию в следующие этапы:
- Весь код Compose следует писать с помощью AI.
- Дорабатывайте его, обрабатывая граничные случаи и устраняя пробелы в производительности, пока пользовательский интерфейс не будет выпущен для реальных пользователей в рамках публичного тестирования.
Разделение работы на два этапа для каждого экрана позволяет одному инженеру быстро пройти весь путь, заранее определив архитектуру и сложные граничные случаи. Благодаря этой подготовительной работе другие могут сосредоточиться на подготовке пользовательского интерфейса к производству, не отвлекаясь на принятие технических решений, что ускоряет весь процесс миграции.
Результаты миграции подтвердили правильность выбранного подхода. Для перенесенных интерфейсов Instagram Direct использование Jetpack Compose позволило команде сократить общий объем кода пользовательского интерфейса на 50% . Меньшее количество кода, генерируемого ИИ, связано с более высоким качеством результата и меньшей стоимостью токена за задачу.

Внутренний анализ данных кода Android для Instagram Direct сравнил сеансы работы ИИ-агента в Compose UI с выполнением тех же задач с использованием Android Views. Повышение эффективности было очевидным по двум параметрам:
- В расчете на один символ внедренного кода: Compose потребовал на 32% меньше обменов данными между инженером и агентом и на 35% меньше времени выполнения агента (время, прошедшее с момента начала работы агента над запросом инженера до получения ответа).
- В расчете на одну сессию агента: общая стоимость токенов снизилась на 33% при использовании Compose по сравнению с Views.
Мы приводим данные как об эффективности выполнения, так и о типичном количестве сессий, поскольку они являются независимыми полезными показателями. Показатели обмена данными между инженером и агентом, а также времени выполнения сравнивают использование ресурсов на единицу полученного результата, в то время как показатель количества токенов сравнивает общую стоимость типичной сессии работы агента.
Полученные данные также выявили устойчивую разницу в том, как две платформы обрабатывают сложный или уязвимый код. Meta отслеживает это с помощью оценки риска изменений кода, которая оценивает общее качество кода и вероятность того, что изменение вызовет инциденты в производственной среде. Анализ измерял эффективность использования ресурсов агента, используя совокупность потребления токенов, времени выполнения агента и взаимодействия инженера с агентом. По мере накопления файлов с более высоким показателем риска сеансы работы ИИ-агента естественным образом становятся менее ресурсоэффективными .
Когда накопленный показатель риска файла удваивается , пользовательский интерфейс, реализованный с помощью Android Views, снижает эффективность использования ресурсов агента на 30% (на каждого приземлившегося персонажа). В тех же условиях снижение эффективности с помощью Jetpack Compose UI составляет всего 9%.
Благодаря партнерству Google и Meta, команда Instagram Direct привнесла свежий взгляд на внедрение Compose — рассматривая его с точки зрения готовности кода к использованию ИИ, а не просто переписывая пользовательский интерфейс. Эта работа выявила сильные стороны Compose как основы для создания кодовых баз и архитектур, ориентированных на ИИ, особенно при применении в масштабах таких приложений, как Instagram.
Оптимизация производительности
Instagram Direct — один из важнейших интерфейсов приложения, и пользователи ожидают, что он будет работать быстро и отзывчиво в любое время. Внедрение Jetpack Compose фактически означало существенную переработку пользовательского интерфейса, и главной целью было сохранение высокого качества работы без каких-либо регрессий .
Многолетние итерации уже позволили довести устаревшую реализацию интерфейса на основе представлений в Instagram до исключительно высокого уровня производительности, и команде нужно было соответствовать этому же стандарту, переходя при этом на совершенно новую структуру пользовательского интерфейса.
Instagram измеряет сотни, если не тысячи, показателей эффективности. Для внедрения Compose наиболее важными оказались следующие три:
- Время взаимодействия — промежуток времени между открытием экрана и возможностью его использования.
- Время полной загрузки — промежуток времени между открытием экрана и полной загрузкой всего контента (например, изображений).
- Производительность прокрутки — насколько плавно происходит прокрутка экрана, без выпадения кадров.
Эти метрики отслеживаются во время выполнения в производственной среде, что позволяет проводить A/B-тесты, сравнивая перенесенный пользовательский интерфейс Compose с устаревшим интерфейсом, и оценивать влияние этих усилий на производительность.
Обычно при миграции такого рода начинают с малого: переносят несколько компонентов пользовательского интерфейса, собирают данные и изучают их поведение. Хотя эти предварительные результаты полезны, они дают лишь частичную картину и могут привести к ложным отрицательным выводам относительно внедрения Compose, поскольку:
- Данные не являются репрезентативными — один перенесенный компонент пользовательского интерфейса может предоставить полезную информацию о своей общей производительности на конкретном экране. Однако разные компоненты ведут себя по-разному по причинам, которые нельзя обобщить, поэтому нельзя всегда делать выводы на их основе.
- Затраты на взаимодействие — небольшой фрагмент кода Compose внутри большого кода View влечет за собой непредсказуемые затраты на обеспечение связи между двумя системами. Эти накладные расходы искажают результаты измерений, поэтому предварительные результаты, полученные в небольших масштабах, не отражают того, как будет выглядеть полная миграция.
В результате, небольшие миграции, хотя и полезные, не всегда отражают весь потенциал Compose. Чем больше поверхности переносится от начала до конца без прерываний, тем яснее и лучше становится картина с точки зрения производительности.
Основные экраны в Instagram Direct построены на основе длинных списков различных типов элементов, первоначально реализованных с помощью RecyclerView . Архитектура опирается на пользовательские абстракции для масштабируемости, но при этом остается привязанной к жизненному циклу системы, основанной на View.

Основной задачей команды был постепенный перенос нескольких сотен отдельных элементов списков в Compose в рамках существующей архитектуры на основе RecyclerView , с последующим внедрением в рабочую среду небольшими независимыми группами в рамках A/B-тестирования — и все это без видимых изменений в пользовательском интерфейсе обмена сообщениями.
Самый большой недостаток такой конфигурации — значительная зависимость от устаревшей системы View через базовую архитектуру RecyclerView , даже после полной миграции всех элементов списка в Compose. В качестве естественного следующего шага команда решила инвестировать в замену базовой архитектуры на основе RecyclerView на альтернативу, разработанную непосредственно в Compose, — LazyColumn .
Это означает, что компоненты Compose UI должны быть абстрагированы от фреймворка, в который они заключены, и при этом оставаться совместимыми как с RecyclerView , так и с LazyColumn . Не менее важна возможность переключения между ними во время выполнения с помощью флагов функций, что позволяет проводить A/B-тестирование.

Хотя новые элементы Compose изначально совместимы с LazyColumn и могут быть интегрированы в непрерывное дерево композиции, был создан API для их интеграции и в RecyclerView . Это позволило провести A/B-тестирование LazyColumn параллельно с RecyclerView — повторно используя те же элементы Compose и улучшая производительность, не нарушая работу остальных функций построения и совершенствования команды.
Масштаб, сложность и чувствительность Instagram даже к самым незначительным регрессиям представляли собой уникальную проблему для Jetpack Compose. Для решения этих задач потребовалось итеративное , практическое сотрудничество . Работая в тесном взаимодействии, инженеры Google и Meta анализировали метрики, чтобы точно определить и разработать новые возможности Compose, соответствующие или превосходящие показатели, основанные на просмотре. В результате этого сотрудничества были реализованы следующие нововведения в Jetpack Compose: возможность приостановки создания с помощью LazyLayoutCacheWindows и отслеживание видимости.
Приостанавливаемая композиция с использованием LazyLayoutCacheWindows
Функция паузируемой композиции (включена по умолчанию в Compose 1.10 ) позволяет постепенно компоновать дорогостоящие элементы ленивого списка между фреймами, предотвращая рывки. В сочетании с LazyLayoutCacheWindow (добавленной в Compose 1.9 ) эта комбинация значительно улучшает плавность прокрутки. В ходе недавних внутренних тестов в Meta, сочетание паузируемой композиции с LazyLayoutCacheWindow , занимающей один экран, снизило количество больших пропусков кадров в минуту (LFDs/m) примерно на 13% по сравнению со стандартным Compose. Cache Window сама по себе снизила это значение примерно на 8% по сравнению с тем же базовым показателем. LFDs/m — это внутренний показатель, используемый Meta для отслеживания заметных рывков при прокрутке.

Использование LazyLayoutCacheWindow в вашем приложении подготавливает и сохраняет элементы за пределами экрана в пиксельной полосе вокруг области просмотра, что позволяет быстро перемещать их. Чтобы воспользоваться преимуществами LazyLayoutCacheWindows в вашем приложении, вы можете использовать последнюю версию Compose 1.13.0-alpha03 и настроить ее, как показано в примере ниже:
val cacheWindow = LazyLayoutCacheWindow(ahead = 150.dp, behind = 100.dp) // OR val cacheWindow = LazyLayoutCacheWindow(aheadFraction = 0.5f, behindFraction = 0.3f) LazyColumn(state = state, cacheWindow = cacheWindow) { ... }
Существует два способа настройки окна кэширования. Оба описывают одно и то же: сколько контента, находящегося за пределами экрана, следует сохранять, но в разных единицах измерения.
- Dp : фиксированная абсолютная длина.
ahead = 150.dpсохраняет 150dp контента, расположенного за видимым краем, независимо от устройства. - Float : доля области просмотра.
aheadFraction = 0.5fпозволяет разместить половину экрана впереди, поэтому абсолютное значение масштабируется в зависимости от высоты экрана, поддерживая различные форм-факторы: больше на планшете или в разложенном виде, меньше на компактном телефоне.
Команда Instagram точно настроила параметры плавающей запятой для окна кэширования специально под структуру контента и размеры элементов Direct. Поскольку оптимальные значения варьируются в зависимости от конкретных параметров пользовательского интерфейса, для поиска правильного баланса требуется некоторое экспериментирование.
Регистрация показов с помощью onVisibilityChanged
Он onVisibilityChanged (Добавлено в Compose 1.9.0) API стал еще одним ключевым результатом технического партнерства между Google и Meta. Он предоставляет крупномасштабным панелям Jetpack Compose согласованный способ определения того, когда элемент композиции действительно виден на экране, заменяя пользовательские, созданные вручную реализации, использовавшиеся ранее. Только в Instagram Direct эти сигналы видимости используются для сотен файлов для поддержки метрик качества продукта, которые зависят от того, были ли элементы пользовательского интерфейса фактически показаны пользователям.
Результаты работы стартапа
Внедрение Jetpack Compose в Instagram Direct привело к неожиданным улучшениям производительности на других платформах приложения. Затраты на запуск Jetpack Compose оплачиваются только один раз, и поскольку мессенджер является высокопосещаемой платформой, часто используемой в начале пользовательской сессии, на других платформах Instagram, использующих Compose, наблюдалось заметное улучшение производительности.
Оптимизация производительности запуска Compose UI внутри самого Instagram Direct была достигнута за счет использования базовых профилей , которые предварительно компилируют наиболее часто используемые участки кода во время установки, благодаря чему Compose быстро отображается с первого же запуска.
Уроки, извлеченные из перехода с Instagram Direct на Jetpack Compose.
- Jetpack Compose обеспечивает немедленную окупаемость инвестиций: вам не нужно использовать сложные рабочие процессы на основе ИИ, чтобы получить выгоду от Compose. Сокращение объема кода примерно на 50% означает меньше кода для поддержки и уменьшение вероятности возникновения ошибок.
- Разработка архитектуры, изначально предназначенной для ИИ, привела к значительным успехам , включая сокращение времени выполнения ИИ-агентства на 35%, уменьшение количества обменов данными между инженером и агентом на 32% и снижение стоимости токенов на 33%.
- Несмотря на наличие множества API для взаимодействия и поддержки объединения View и Compose, рекомендуется переносить большие поверхности на отдельные небольшие компоненты . Это позволит сохранить пользовательский интерфейс в рамках единой, непрерывной иерархии композиции и раскрыть все лучшие возможности оптимизации производительности, присущие Compose.
- Используйте Pausable composition в паре с LazyLayoutCacheWindow : сочетание этих двух инструментов дает лучшие результаты, чем использование только окна кэша. При использовании только окна кэша даже ресурсоемкий элемент может попытаться скомпоноваться за один проход, потенциально превысив допустимый объем кадров.
- Внесите свой вклад в Compose! Meta сотрудничает с командой Jetpack Compose, чтобы воплотить их отзывы и идеи в жизнь в рамках Compose. Работа над инструментом с открытым исходным кодом означает, что все мы получаем выгоду, когда ошибки и улучшения производительности вносятся централизованно. Поэтому, поделитесь своим мнением !
Внедрение Jetpack Compose позволило добиться значительных успехов в разработке с использованием ИИ, одновременно упростив повседневную работу над пользовательским интерфейсом в Instagram. Декларативный подход уменьшает количество шаблонного кода, упрощает понимание состояния и повышает общую производительность разработчиков. Команда разработчиков Instagram с нетерпением ждет возможности внедрить Compose на большем количестве платформ приложения, а также продолжения сотрудничества между Google и Meta для улучшения работы Instagram и пользователей Jetpack Compose.
Если вы еще не пробовали Compose , то теперь, благодаря поддержке ИИ, переход на Jetpack Compose стал проще, чем когда-либо.
Благодарности. Благодарим Михала Зелински и Мэтью Ду из Meta, а также Андрея Шикова и Джорджа Маунта из Google за их работу по улучшению производительности Compose благодаря сотрудничеству Meta и Google! Благодарим также Гэри Йе из Meta за помощь в интеграции Compose в Instagram Direct и Гопала Джунеджу из Meta за поддержку этой работы с помощью анализа данных!
Примеры из практикиWhatsApp — крупнейшая в мире платформа для обмена сообщениями, которой пользуются миллиарды людей по всему миру. Это основной инструмент общения для людей из разных регионов, соединяющий пользователей посредством приватного, надежного и безопасного обмена сообщениями.
Niharika Arora , Tracy Agyemang , Mayank Jain • Чтение 8 минут
Примеры из практикиМиссия Tinder — способствовать установлению и развитию настоящих связей, делая знакомства простыми и увлекательными для каждого нового поколения одиноких людей.
Ajesh Pai , Ulises Uriel Verduzco Díaz , Tracy Agyemang • Чтение 4 минуты
Примеры из практикиПоскольку большинство приложений для Android используют Kotlin в качестве основного языка программирования, библиотека kotlinx.coroutines стала де-факто стандартом для асинхронного программирования. Эта библиотека предлагает хорошо продуманный и структурированный способ управления параллельными потоками, который является неотъемлемой частью Kotlin.
Jonathan Starup , Andrei Shikov • чтение на 7 минут
Получайте еженедельно самые свежие новости о разработке Android прямо на свою электронную почту.




