Como os engenheiros do Instagram Direct criaram uma arquitetura de interface nativa de IA com o Jetpack Compose e reduziram o custo de token por sessão de agente em 33%
Leitura de 11 minutos
Esta postagem do blog foi escrita em colaboração com a equipe da Meta
O Instagram Direct é uma das principais plataformas do Instagram, processando bilhões de mensagens de usuários todos os dias. Ao longo de anos de iteração, a equipe extraiu todas as micro-otimizações possíveis do sistema Android View legado. No entanto, manter e expandir uma plataforma legada altamente otimizada cria um débito técnico e uma sobrecarga de engenharia significativos, especialmente à medida que as equipes adotam cada vez mais a interface declarativa e os assistentes de programação de IA.
A adoção do Jetpack Compose no Instagram Direct foi além de uma modernização típica da interface. A equipe criou uma base de código de interface nativa de IA 50% menor do que a implementação original, além de uma redução de 35% no tempo de execução do agente de IA, 32% menos trocas entre engenheiro e agente e uma redução de 33% no custo de token. Em parceria com o Google, a equipe adotou o Jetpack Compose, mantendo um alto nível de performance. Com as otimizações de performance, a Meta e o Google melhoraram o Compose não apenas para o Instagram, mas também para o ecossistema de desenvolvedores Android em geral.
Modernização da base de código em grande escala
A IA se tornou rapidamente uma companheira diária para engenheiros do setor, e aplicá-la a uma base de código em grande escala como o Instagram já gera ganhos reais de produtividade. A equipe do Instagram Direct estabeleceu uma meta mais ambiciosa. Em vez de apenas apontar ferramentas de IA para o código existente, a equipe redesenhou a base de código e a arquitetura para serem nativas de IA por design, multiplicando o impacto da IA muito além do que a adaptação por si só pode oferecer.
A equipe do Instagram Direct escolheu o Jetpack Compose como um componente essencial para criar uma arquitetura de interface nativa de IA. A natureza declarativa garante que o código seja conciso, previsível e estruturalmente mais fácil para os modelos de IA raciocinarem, com menos efeitos colaterais, menos estado implícito e limites de componentes mais claros.
A migração para o Jetpack Compose exigiu um planejamento cuidadoso. Centenas de milhões de pessoas enviam mensagens no Instagram todos os dias. Por isso, a migração precisou ser gradual e tranquila, sem interrupções na experiência enquanto a equipe reestruturava a base. Para ilustrar a dimensão do desafio: componentes individuais da interface podem ser renderizados em mais de 160 permutações de estado distintas, e uma única tela de conversa processa mais de 200 tipos de mensagens diferentes.
Ao migrar uma base de código desse tamanho para o Compose, é tentador pegar o caminho mais fácil e incorporar componentes da interface do Compose na hierarquia de visualização atual. Como uma etapa incremental durante uma migração gradual, isso é perfeitamente válido. No entanto, a longo prazo, integrar o Compose em uma base de código baseada em visualizações é um desafio. As ferramentas de IA geralmente seguem o caminho mais fácil. Se você misturar código de interface declarativo e imperativo, é provável que a IA os combine de forma incorreta, introduzindo bugs sutis, dívida técnica e regressões de desempenho.
Como criar uma arquitetura de interface nativa de IA
Na escala do Instagram, um grau de abstração arquitetônica é inevitável e é o que mantém o app sustentável à medida que ele cresce. Considere um padrão comum, em que cada tipo de item RecyclerView é modelado como um descendente de uma classe base RecyclerViewItem personalizada que expõe hooks de ciclo de vida comuns, como onBind.
Exemplo 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") } } ... } } }
No snippet acima, dois problemas aparecem. Primeiro, a flag isPinnedChatsEnabled é lida em código imperativo e capturada em uma lambda do Compose, um acoplamento sutil entre paradigmas. Em segundo lugar, isPinned existe como um campo mutável no próprio item, em vez de ChatUiState. Assim, ele sobrevive à nova vinculação e reciclagem de RecyclerView em várias linhas, vazando e produzindo bugs difíceis de reproduzir.
Mesmo quando o código é limpo ao atribuir ao item uma função @Composable dedicada, os mesmos problemas permanecem.
Exemplo 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") } } ... } }
Este é um exemplo simples, mas ilustra um problema mais amplo: quanto menos limites a IA recebe, menor é a qualidade do código que ela produz ao longo do tempo. As proteções e habilidades ajudam, mas não são suficientes por si só, porque quando a IA encontra um obstáculo, ela geralmente o contorna para se desbloquear.
Para que a base de código seja compatível com a IA, ela precisa seguir duas regras práticas:
- Minimizar a dependência do contexto personalizado. Quanto mais conhecimento específico sobre a base de código um agente de IA precisa para fazer uma mudança correta, menor é a qualidade da saída. Quanto mais próxima a base de código estiver das práticas recomendadas conhecidas, melhores serão os resultados da IA.
- Uma base de código focada na IA precisa impor os próprios limites. Corrigir falhas de design com habilidades de IA não é escalonável, já que cada habilidade carregada no contexto custa tokens e pode prejudicar o desempenho do agente. Em vez disso, a própria arquitetura deve ter esse peso. Os agentes de IA naturalmente seguem o caminho de menor resistência. Por isso, o design precisa fazer com que esse caminho leve a um código correto e de alta qualidade, dificultando e encarecendo a expressão de decisões de design ruins.
Um item de lista ainda pode ser representado pela própria abstração, mas, nesse caso, todo o código do Compose fica no construtor. Assim, ele não tem acesso a membros ou estados da classe, e a única fonte de argumentos é o construtor. Isso a torna equivalente a uma função @Composable simples, ao mesmo tempo em que está em conformidade com a arquitetura atual.
Exemplo 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") } } ... }, )
Migrar uma base de código desse tamanho é uma tarefa enorme. Por muito tempo, os centenas de componentes de interface que compõem a maioria da interface direta tiveram que coexistir com as contrapartes legadas, e ambos foram mantidos em paralelo. Os fluxos de trabalho de IA ajudaram a tornar possível essa migração paralela ao acelerar o processo de gravação de grandes quantidades de código. Essa abordagem permitiu que a equipe do Direct fizesse a migração em tempo recorde, sem interromper o restante da equipe, que continuou lançando os recursos que melhoram a experiência de milhões de pessoas todos os dias.
Vários engenheiros executaram os próprios agentes de IA em uma base de conhecimento compartilhada de habilidades e convenções reutilizáveis criadas durante a migração. Isso manteve os fluxos de trabalho e as práticas recomendadas sincronizados em toda a equipe, em vez de cada engenheiro ter que redescobri-los. Em cada plataforma, a equipe realizou a migração nas seguintes etapas:
- Escrever todo o código do Compose com IA.
- Refinamos o produto, lidando com casos extremos e fechando lacunas de desempenho, até que a interface fosse lançada para usuários reais em um teste público.
Dividir o trabalho em duas etapas por tela permite que um engenheiro avance rapidamente por toda a superfície, resolvendo a arquitetura e os casos extremos complicados desde o início. Com essa base estabelecida, outras pessoas podem se concentrar em deixar a interface pronta para produção sem precisar parar para tomar essas decisões técnicas, mantendo a migração geral rápida.
Os resultados da migração validaram a abordagem. Para as plataformas migradas do Instagram Direct, o Jetpack Compose permitiu que a equipe reduzisse a quantidade total de código da interface em 50%. Quanto menos código a IA precisar gerar, maior será a qualidade da saída e menor será o custo de token por tarefa.
Uma análise interna de dados da base de código do Android para o Instagram Direct comparou sessões de agentes de IA trabalhando na interface do Compose com as mesmas tarefas usando o Android Views. Os ganhos de eficiência foram claros em duas dimensões:
- Por caractere de código inserido:o Compose exigiu 32% menos trocas entre engenheiro e agente e 35% menos tempo de execução do agente(o tempo decorrido desde que um agente começa a trabalhar na solicitação de um engenheiro até que ele retorne uma resposta).
- Por sessão de agente:o custo geral de token caiu 33% com o Compose em comparação com o Views.
Informamos a eficiência de saída e os números típicos de sessão porque são resultados úteis de forma independente. Os números de troca e tempo de execução do engenheiro-agente comparam o uso de recursos por unidade de saída gerada, enquanto o número de tokens compara o custo total de uma sessão típica do agente.
Os dados também revelaram uma diferença consistente em como as duas estruturas processam códigos complexos ou frágeis. A Meta rastreia isso usando uma pontuação de risco de mudanças no código, que avalia a qualidade geral do código e a probabilidade de uma mudança causar incidentes de produção. A análise mediu a eficiência de recursos de um agente usando um composto de consumo de tokens, tempo de execução do agente e interações entre engenheiro e agente. À medida que os arquivos acumulam uma pontuação de risco mais alta, as sessões do agente de IA naturalmente se tornam menos eficientes em termos de recursos.
Quando a pontuação de risco acumulada de um arquivo dobra, a interface implementada com Android Views reduz a eficiência de recursos do agente em 30% (por caractere de destino). Nas mesmas circunstâncias, a redução da interface do Jetpack Compose é de apenas 9%
Com a parceria entre o Google e a Meta, a equipe do Instagram Direct trouxe uma nova perspectiva para a adoção do Compose, abordando-o pela prontidão da IA da base de código, não apenas pela reescrita da interface. Esse trabalho revelou a força do Compose como base para criar arquiteturas e bases de código com foco em IA, principalmente quando aplicado na escala de apps como o Instagram.
Otimizações de performance
O Instagram Direct é uma das plataformas mais importantes do app, e as pessoas esperam que ele seja rápido e responsivo em todos os momentos. Adotar o Jetpack Compose significou uma reescrita substancial da interface, e o objetivo principal era preservar a experiência de alta qualidade sem regressões.
Anos de iteração já haviam elevado a implementação legada baseada em visualizações no Instagram a um nível de desempenho excepcionalmente alto, e a equipe precisava atender a esse mesmo padrão ao migrar para uma estrutura de interface totalmente nova.
O Instagram mede centenas, se não milhares, de métricas de performance. Para a adoção do Compose, os três a seguir foram os mais importantes:
- Tempo para interação: o tempo entre abrir a tela e poder usá-la.
- Tempo para carregamento completo : o tempo entre a abertura da tela e o carregamento completo de todo o conteúdo (ou seja, imagens).
- Desempenho de rolagem: a fluidez da rolagem da tela, sem perda de frames.
Essas métricas são rastreadas em tempo de execução na produção, permitindo executar testes A/B comparando a interface do Compose migrada com a interface legada e avaliar o impacto no desempenho desse esforço.
Uma maneira comum de abordar uma migração como essa é começar pequeno, movendo alguns componentes da interface, coletando dados e estudando o comportamento deles. Embora sejam úteis, esses primeiros resultados mostram apenas uma imagem parcial, fornecendo falsos negativos contra a adoção do Compose porque:
- Não representativo: um componente de interface migrado pode fornecer dados úteis sobre a performance geral dele em uma tela específica. No entanto, diferentes componentes se comportam de maneira diferente por motivos que não são generalizados. Portanto, nem sempre é possível extrapolar a partir disso.
- Custo de interoperabilidade: um pequeno trecho do Compose dentro de uma grande base de código do View paga um custo de ponte imprevisível entre os dois sistemas. Esse excesso distorce a medição. Por isso, os primeiros resultados em pequena escala não refletem como seria uma migração completa.
O resultado é que migrações pequenas, embora úteis, nem sempre refletem o impacto total do Compose. Quanto mais uma superfície é migrada de ponta a ponta sem interrupções, mais clara e melhor fica a imagem em termos de performance.
As telas principais do Instagram Direct são criadas com base em longas listas de vários tipos de itens, originalmente implementadas com RecyclerView. A arquitetura depende de abstrações personalizadas para escalonabilidade, mas continua vinculada ao ciclo de vida do sistema baseado em visualizações.
A principal tarefa da equipe foi uma migração gradual de várias centenas de itens de lista individuais para o Compose na arquitetura baseada em RecyclerView, lançando-os na produção em pequenos grupos independentes em testes A/B. Tudo isso sem mudanças visíveis na experiência de mensagens do usuário.
A maior desvantagem dessa configuração é uma dependência significativa do sistema View legado por meio de uma arquitetura RecyclerView principal, mesmo após a migração completa de cada item da lista para o Compose. Como uma próxima etapa natural, a equipe decidiu investir na substituição da arquitetura principal baseada em RecyclerView pela alternativa nativa do Compose, o LazyColumn.
Isso significa que os componentes da UI do Compose precisam ser abstraídos do framework em que estão incluídos, mas ainda serem compatíveis com RecyclerView e LazyColumn ao mesmo tempo. Também é importante poder alternar entre os dois em tempo de execução usando flags de recursos para ativar os testes A/B.
Embora os novos itens do Compose sejam compatíveis de forma nativa com o LazyColumn e possam ser conectados a uma árvore de composição ininterrupta, uma API de interoperabilidade foi criada para inseri-los também em um RecyclerView. Isso permitiu lançar a configuração do LazyColumn em um teste A/B lado a lado com o RecyclerView, reutilizando os mesmos itens do Compose e melhorando a performance sem interromper o restante da equipe que estava criando e refinando recursos.
A escala, a complexidade e a sensibilidade do Instagram até mesmo às menores regressões representaram um desafio único para o Jetpack Compose. Para resolver esses problemas, foi necessário uma parceria iterativa e prática. Trabalhando em conjunto, os engenheiros do Google e da Meta analisaram métricas para identificar e projetar novos recursos do Compose que atendessem ou superassem os comparativos de mercado baseados em visualizações. Como resultado dessa parceria, destacamos as seguintes adições ao Jetpack Compose: composição pausável com LazyLayoutCacheWindows e rastreamento de visibilidade.
Composição pausável com LazyLayoutCacheWindows
A composição pausável (ativada por padrão no Compose 1.10) permite que itens caros de listas lentas sejam compostos incrementalmente em vários frames para evitar instabilidade. Quando pareado com LazyLayoutCacheWindow (adicionado no Compose 1.9), a combinação melhora significativamente a suavidade da rolagem. Em testes internos recentes na Meta, a combinação da composição pausável com um LazyLayoutCacheWindow de uma janela de visualização reduziu as quedas de frames grandes por minuto (LFDs/m) em cerca de 13% em comparação com o Compose sem modificações. A janela de cache por si só reduziu o uso em cerca de 8% em relação ao mesmo valor de referência. LFDs/m é uma métrica interna usada pela Meta para rastrear travamentos perceptíveis durante a rolagem.
Usar um LazyLayoutCacheWindow no app prepara e retém itens fora da tela em uma banda baseada em pixels ao redor da janela de visualização para permitir gestos rápidos. Para aproveitar o LazyLayoutCacheWindows no seu app, use a versão mais recente do Compose 1.13.0-alpha03 e configure-o conforme mostrado no exemplo abaixo:
val cacheWindow = LazyLayoutCacheWindow(ahead = 150.dp, behind = 100.dp) // OR val cacheWindow = LazyLayoutCacheWindow(aheadFraction = 0.5f, behindFraction = 0.3f) LazyColumn(state = state, cacheWindow = cacheWindow) { ... }
Há duas maneiras de configurar a janela de cache. Ambos descrevem a mesma coisa: quanto conteúdo fora da tela manter composto, mas em unidades diferentes.
- Dp: comprimento absoluto fixo. O
ahead = 150.dpmantém 150 dp de conteúdo composto além da borda visível, independente do dispositivo. - Flutuante: fração da janela de visualização. O
aheadFraction = 0.5fmantém metade de uma tela composta à frente. Assim, a quantidade absoluta é dimensionada com a altura da tela, oferecendo suporte a vários formatos: mais em um tablet ou dobrável desdobrado, menos em um smartphone compacto.
A equipe do Instagram ajustou as frações de ponto flutuante da janela de cache especificamente para a estrutura de conteúdo e os tamanhos de itens do Direct. Como os valores ideais variam de acordo com os parâmetros específicos da interface, encontrar o equilíbrio certo requer alguns testes.
Registro de impressões com onVisibilityChanged
A API onVisibilityChanged (adicionada no Compose 1.9.0) foi outro resultado importante da parceria técnica entre o Google e a Meta. Ele oferece às superfícies do Jetpack Compose em grande escala uma maneira consistente de saber quando um elemento combinável está realmente visível na tela, substituindo implementações personalizadas e manuais usadas no passado. Só no Instagram Direct, esses indicadores de visibilidade são usados em centenas de arquivos para oferecer suporte a métricas de qualidade do produto que dependem da exibição real dos elementos da interface para as pessoas.
Performance de inicialização
A adoção do Jetpack Compose no Instagram Direct resultou em melhorias inesperadas de performance em outras áreas do app. O tempo de execução do Jetpack Compose tem um custo de inicialização que você paga apenas uma vez. Como a troca de mensagens é uma área de alto tráfego, geralmente acessada no início de uma sessão do usuário, outras áreas do Instagram que dependem do Compose tiveram melhorias notáveis de performance.
A performance de inicialização da interface do Compose no Instagram Direct foi otimizada com o uso de perfis de referência, que pré-compilam caminhos de código ativos no momento da instalação para que o Compose seja renderizado rapidamente desde a primeira inicialização.
Lições da migração do Instagram Direct para o Jetpack Compose
- O Jetpack Compose tem um retorno imediato do investimento:não é necessário usar fluxos de trabalho avançados de IA para se beneficiar do Compose. Com a redução de aproximadamente 50% no código, há menos código para manter e uma área de superfície reduzida para bugs.
- Projetar uma arquitetura nativa de IA gerou resultados significativos, incluindo uma redução de 35% no tempo de execução do agente de IA, 32% menos trocas entre engenheiro e agente e uma redução de 33% no custo de token.
- Embora haja muitas APIs de interoperabilidade e suporte para combinar Views e Compose, migre superfícies maiores em vez de componentes pequenos individuais. Isso mantém a interface do usuário em uma única hierarquia de composição ininterrupta e desbloqueia todas as melhores otimizações de performance nativas do Compose.
- Combine a composição pausável com LazyLayoutCacheWindow: essa combinação gera resultados melhores do que janelas de cache isoladas. Com apenas a janela de cache, um item pesado ainda pode tentar compor em uma única transmissão, possivelmente excedendo o orçamento de frames.
- Contribua com o próprio Compose! A Meta fez uma parceria com a equipe do Jetpack Compose para colocar em prática o feedback e as ideias dela no Compose. Trabalhar em um kit de ferramentas de código aberto significa que todos nos beneficiamos quando bugs são corrigidos e melhorias de performance são feitas de forma centralizada. Então, envie seu feedback.
A adoção do Jetpack Compose gerou ganhos significativos no desenvolvimento assistido por IA e simplificou a engenharia de interface diária no Instagram. A abordagem declarativa reduz o boilerplate, facilita o raciocínio sobre o estado e melhora a produtividade geral dos desenvolvedores. A equipe de engenharia do Instagram quer levar o Compose para mais plataformas no app e continuar a colaboração entre o Google e a Meta para trazer mais melhorias para os usuários do Instagram e do Jetpack Compose.
Se você ainda não testou o Compose, agora com assistência de IA, migrar para o Jetpack Compose está mais fácil do que nunca.
Confirmações. Agradecemos a Michal Zielinski e Matthew Du da Meta, e a Andrei Shikov e George Mount do Google, pelo trabalho de trazer melhorias de performance para o Compose com a colaboração entre a Meta e o Google. Agradecemos também a Gary Ye, da Meta, por ajudar a trazer o Compose para o Instagram Direct, e a Gopal Juneja, também da Meta, por apoiar esse esforço com a ciência de dados.
-
Estudos de casoO WhatsApp é a maior plataforma de mensagens do mundo, atendendo a bilhões de usuários em todo o mundo. É a ferramenta de comunicação padrão para pessoas em diversas regiões, conectando usuários por mensagens privadas, confiáveis e seguras.
Niharika Arora, Tracy Agyemang, Mayank Jain • Leitura de 8 minutos -
Estudos de casoO Tinder tem como missão impulsionar e inspirar conexões reais, facilitando e tornando divertido o encontro de pessoas solteiras de todas as gerações.
Ajesh Pai, Ulises Uriel Verduzco Díaz , Tracy Agyemang • Leitura de 4 minutos -
Estudos de casoCom a maioria dos apps Android adotando o Kotlin como linguagem principal, o kotlinx.coroutines se tornou um padrão de fato para programação assíncrona. A biblioteca oferece uma maneira bem projetada e estruturada de gerenciar fluxos simultâneos nativos do Kotlin.
Jonathan Starup, Andrei Shikov • Leitura de 7 minutos
Receba os insights mais recentes sobre desenvolvimento Android na sua caixa de entrada semanalmente.