AppgeousGuias práticos sobre aplicativos e inovação
Desenvolvimento Mobile

XML vs. Jetpack Compose: O Que Muda na Memória e no Estado da UI

Entenda como a recomposição do Jetpack Compose difere das atualizações imperativas do XML e o impacto disso no consumo de memória do seu app Android.

Bruno Carvalho Ramos
Bruno Carvalho RamosEditor Chefe de Desenvolvimento Mobile6 min de leitura
Imagem editorial ilustrando XML vs. Jetpack Compose: O Que Muda na Memória e no Estado da UI

A migração mental de Views XML para Jetpack Compose costuma travar em um ponto específico que não é sintaxe, mas arquitetura. O desenvolvedor acostumado afindViewById e textView.setText tende a procurar o equivalente no Compose. O erro crasso é tentar "puxar" os dados para a tela manualmente como se fazia em 2018. Em 2026, com o Compose estável e dominando a nova base de código do Android, entender a diferença entre atualização imperativa e recomposição é o que separa um app que trava daquele que roda a 60 FPS mesmo em aparelhos de entrada.

O segredo está em como a memória é alocada e como a framework decide o que redesenhar. Não é apenas uma forma diferente de escrever layout; é uma mudança radical de quem controla o estado da interface.

O Modelo Imperativo e o Custo de Sincronia

No sistema tradicional baseado em XML (Views), a tela é um objeto pesado que já existe. Quando você recupera uma View via ID, está segurando uma referência direta a um componente que ocupa memória constante. Se o usuário clica em um botão e o saldo bancário precisa mudar de R$ 100,00 para R$ 150,00, você precisa explicitamente dizer ao componente para mudar.

Se você esquece de chamar textView.text = novoSaldo no callback de sucesso da API, o usuário continua vendo o valor antigo. Isso gera um problema clássico de sincronia: a lógica de negócios atualizou, mas a UI ficou "desconectada" da realidade. Pior, como a hierarquia de Views é um objeto mutável que vive na memória o tempo todo, qualquer manipulação direta pode levar a vazamentos de memória (memory leaks) se você segurar uma referência de Activity por tempo demais.

Imagine um app de delivery como o iFood. Em uma lista de restaurantes, cada item é uma View. Se o preço de um hambúrgurger muda no servidor, o app imperativo precisa percorrer a lista, achar o adapter, encontrar o item na posição específica e notificar o dataset. É manual, trabalhoso e propenso a erros quando a complexidade da tela aumenta.

A Recomposição do Jetpack Compose

O Jetpack Compose inverte a lógica. Aqui, a interface é uma função do estado. O @Composable não é um objeto que fica parado na memória esperando ordens; é uma função que pode ser executada várias vezes. Quando o estado muda, a função roda de novo.

Parece ineficiente redesenhar a tela inteira a cada mudança de dado, mas é aí que a mágica do "Smart Recomposition" entra em jogo. O Compose não recria tudo cegamente. Ele usa um sistema de posições (slots) para lembrar o que foi desenhado antes e compara os parâmetros de entrada.

Se você tem um Text(text = saldo) e o saldo muda, o Compose sabe que apenas aquele nó de texto precisa ser atualizado. O resto da árvore de composição permanece intocada. Ele descarta a instrução antiga e injeta a nova, sem precisar alocar novos objetos pesados para a View inteira, apenas atualizando as propriedades do renderer底层. Isso economiza ciclos de CPU e reduz a pressão no Garbage Collector, algo vital em dispositivos com apenas 4GB de RAM que ainda constituem uma fatia grande do mercado brasileiro.

Detalhe fotográfico relacionado a XML vs. Jetpack Compose: O Que Muda na Memória e no Estado da UI

Essa abordagem elimina a necessidade de buscar Views por ID e atualizá-las. O estado é a "fonte da verdade". Você muda o estado, a UI se adapta sozinha. O risco de mostrar dados desatualizados cai drasticamente, pois não existe como a UI ficar estática se ela está atrelada diretamente a um MutableStateFlow ou LiveData observado.

Impacto Real na Memória do Dispositivo

Aqui entramos na especificidade técnica que afeta o desempenho do usuário final. No sistema de Views, a criação de layout custa caro. Inflar um XML complexo com ConstraintLayout aninhado consome tempo de thread principal e alocação de objetos. Uma vez criados, eles ficam lá.

No Compose, a estrutura de "Composition" é muito mais leve que a hierarquia de Views. Ela usa uma tabela de slots interna para guardar os dados necessários para redesenhar, dispensando a necessidade de manter instâncias de objetos View pesados para cada elemento invisível ou fora da tela.

Contudo, existe uma armadilha de memória no Compose que não existe no XML: a recomposição excessiva. Se você cria uma Composable Function que recebe um objeto instável como parâmetro (uma classe de dados que não implementa equals/hashCode corretamente, ou uma List comum ao invés de SnapshotStateList), o Compose não consegue otimizar. Ele acha que "tudo mudou" e reexecuta a função inteira. Isso pode gerar alocações frenéticas de objetos temporários, travando a UI.

Já vi casos onde um desenvolvedor passava uma lista inteira de itens de um e-commerce para uma Row. A cada novo item adicionado, a lista inteira era recriada, causando 60 recomposições por segundo em uma tela estática. O impacto na bateria e na temperatura do aparelho era imediato. A correção foi usar LazyRow e garantir que a lista fosse imutável ou gerenciada pelo estado, reduzindo o uso de CPU para próximo de zero em repouso.

Quando o "Lembrete" do Compose Vira Ineficiência

O erro clássico de quem vem do XML é tentar "lembrar" o estado da View dentro do Composable usando variáveis locais comuns. Se você faz var contador = 0 dentro de uma função @Composable, aquela variável é reiniciada a cada recomposição. A UI nunca vai incrementar.

O desenvolvedor então tenta hackear isso mantendo o estado em um objeto singleton ou em uma classe separada sem usar os ferramentais corretos (remember, rememberSaveable ou ViewModel). Isso quebra o ciclo de vida da composição. Se o usuário gira a tela (mudança de configuração), a Activity morre, mas o estado singleton sobrevive, e a UI pode tentar se renderizar com dados nulos, causando um crash.

Gerenciar o estado corretamente significa elevar a o estado (State Hoisting) para um nível que sobreviva às recomposições. Geralmente, isso coloca o estado no ViewModel ou em um holder que persiste. A UI se torna "boba", apenas refletindo o que o ViewModel diz.

Essa separação rígida entre UI e estado facilita muito testes e manutenção a longo prazo. Se você trabalha em equipe, saber que a View não guarda lógica de negócios evita aquele famoso "bug do sexta-feira" onde um botão para de funcionar porque alguém mexeu no onClick de forma isolada.

Se a sua base de código ainda está misturando os dois mundos, saiba que é possível conviver com ambos, mas o custo cognitivo aumenta. Para novos projetos em 2026, a recomposição do Compose oferece uma economia de código significativa, mas exige disciplina na criação de classes estáveis e no gerenciamento de escopo de corrotinas. Às vezes, a otimização chega a trocar a linguagem nativa, como vimos quando migramos nosso código Flutter para Rust, mas no Android puro, dominar o Compose é o melhor caminho para performance nativa.

A chave para não se perder é parar de pensar em "atualizar a tela" e começar a pensar em "descrever a tela atual em função dos dados". A memória do celular agradece, e o usuário percebe a fluidez.

Leia em seguida