Ferramentas e performance

Estratégias práticas para reduzir o consumo de memória de bitmap do app

Alto-falantes

Sobre

Sobre a palestra

Gerenciar a memória de bitmap é essencial para reduzir os riscos de falta de memória (OOM, na sigla em inglês) em apps Android com muitos recursos visuais. Embora os desenvolvedores saibam que é preciso reduzir o tamanho das imagens, para alcançar a estabilidade do app a longo prazo, é necessário dominar outros dois pilares fundamentais: reutilizar e reciclar. Esta sessão oferece um guia prático para otimizar o desempenho da memória usando ferramentas modernas, bibliotecas de código aberto como Glide e Coil e APIs no nível do sistema.

Nesta sessão, você vai aprender a diagnosticar regressões de memória reais usando o Android vitals, implementar padrões avançados de armazenamento em cache (reutilização) com o Glide e o Coil e liberar memória de forma proativa (reciclagem) usando o callback onTrimMemory.

Resumo

  • O Google Play está lançando limites de desempenho de memória que serão aplicados em fevereiro de 2027. Ultrapassar o limite do 90º percentil afeta diretamente a avaliação de integridade do app e pode reduzir a visibilidade dele na Google Play Store.
  • Os bitmaps se expandem por dimensões e profundidade de cor quando decodificados, o que os torna os maiores consumidores de memória na maioria dos apps. Apenas algumas imagens em resolução máxima podem ocupar centenas de megabytes.
  • Reduza a resolução das imagens e decodifique apenas as regiões visíveis para reduzir o consumo de memória. Solicite dimensões de imagem correspondentes pela CDN ou use APIs como BitmapRegionDecoder em vez de carregar imagens em resolução máxima que são grandes demais.
  • Vários carregadores de imagens multiplicam o uso padrão do cache de heap de memória e duplicam os bitmaps armazenados. Padronize um carregador ou defina orçamentos de memória explícitos em todos os carregadores.
  • Libere a memória de imagens quando o app for para o segundo plano processando onTrimMemory. Interrompa a sondagem em segundo plano e as animações em onStop para evitar o aumento da memória em segundo plano.

Recursos

Gemini Notebook

Tópicos

  • bitmap-optimization
  • android-memory
  • android-vitals
  • image-caching
  • memory-profiler

Transcrição

Introdução e visão geral da memória de bitmap

  • Fung Lam

    Sim, falta cerca de uma hora para o almoço, então fiquem comigo.

  • Sou Fung Lam, engenheiro de relações com desenvolvedores do Android, em Londres.

  • Sim, obrigada.

  • Antes de começar, posso fazer uma pergunta?

  • Quantos de vocês compraram um celular novo este ano?

  • Uau.

  • Uau.

  • Seja sincero, não vou culpar você se não estiver comprando, Pixel.

  • Bem, o novo smartphone é mais caro, certo?

  • Sim, é por isso que alguns de vocês não compraram um smartphone novo.

  • Por que é mais caro?

  • Você sabe?

  • Não.

  • RAM.

  • RAM, sim, sim.

  • Sim, a RAM é o verdadeiro desafio para o Android e para nós, desenvolvedores.

  • Por isso, hoje estou aqui para contar como podemos superar o desafio da memória RAM.

  • No Google I/O e no Google I/O Connect, tivemos algumas sessões sobre memória, desde como o sistema operacional aloca a memória, como você pode analisar seu aplicativo, como você pode otimizar seu código usando o R8 e, claro, eu falei um pouco sobre Bitmap na época.

  • Você pode ver minhas fotos na captura de tela.

  • A otimização de memória é um domínio amplo, que abrange diferentes áreas, como Android Runtime, coletas de lixo, ferramentas de desempenho, alocação de memória de objetos, compilador etc. Nesta sessão de 30 minutos, vou dar algumas informações sobre como otimizar a memória de bitmap.

  • Você pode perguntar: por que Bitmap?

  • Por que devemos focar na memória de bitmap?

  • Porque uma imagem vale mais do que mil palavras.

  • Isso é verdade, e vou explicar o motivo.

  • Na tela de um smartphone, digamos, um Pixel 7, Pixel 9 ou Pixel 11 Pro, a resolução da tela é de cerca de 2.500 x 1.200 ou 1.400.

  • Portanto, para cada pixel, temos 32 bits de profundidade de cor.

  • Para a imagem da tela cheia, não tenho dedos suficientes, mas ela vai ocupar cerca de 15 MB de memória, 50 MB.

  • Então, uma palavra no sistema operacional é como 64 bits, ou seja, 8 bytes.

  • Mil palavras têm apenas 8 MB.

  • Sim, 8K.

  • Então, uma imagem vale mais do que mil palavras.

  • Sim, isso é importante.

  • Uma imagem já tem 15 MB.

  • E se o app tiver 10 ou 100 imagens?

  • É um número grande.

  • Na verdade, o Bitmap é sempre o maior consumidor de memória no seu aplicativo.

  • Quando a imagem é decodificada na memória, como mencionei, ela é expandida de acordo com a dimensão e a profundidade de cor.

  • Não é o tamanho do arquivo, se está em JPEG ou PNG.

  • Não, ele será decodificado e usará a memória.

  • Portanto, apenas algumas imagens em resolução máxima podem facilmente ocupar centenas de megabytes da memória do dispositivo.

  • Em segundo lugar, quando a memória está muito cheia, o sistema operacional precisa gastar mais energia para lidar com o consumo de memória.

  • Isso vai fazer com que a interface pareça instável.

  • Ou o SO também pode encerrar o app para liberar memória.

  • Caso contrário, o app também vai encontrar um erro ou uma exceção de falta de memória.

  • A boa notícia é que essa fase costuma ser uma grande vitória com pouco esforço.

  • A maioria dos picos de memória vem de erros comuns, que podem ser evitados, e podemos corrigir isso sem comprometer a qualidade da imagem.

  • Agora você sabe que a memória de bitmap é importante para seu aplicativo, não apenas para que ele funcione sem problemas, mas também para a estabilidade e a retenção de usuários.

  • Por isso, hoje vamos falar sobre cinco etapas simples para manter seu app funcionando sem problemas.

  • Primeiro, vamos analisar a qualidade técnica do Google Play, o novo requisito de qualidade técnica, que inclui o próximo limite de memória e onde você pode verificar esse limite com facilidade.

  • Em seguida, vou compartilhar um estudo de caso sobre 500 apps Android, como eles usam a memória, se estão fazendo isso da maneira certa ou se há algo que podemos aprender com eles.

  • Depois, vou mostrar o princípio de como otimizar o uso da memória de bitmap.

  • Por fim, vamos compartilhar algumas ferramentas que podem ajudar você a verificar seu próprio app.

Padrões de qualidade do Google Play e problemas comuns

  • Fung Lam

    Primeiro, o novo requisito de qualidade do Google Play.

  • Com que frequência você acessa o Google Play Console?

  • Todo mês?

  • A cada semana?

  • Nunca?

  • Essas risadas significam sim, né?

  • Não?

  • Não.

  • Você acessou a seção do Android vitals?

  • Sim, sim.

  • Sim, então você definitivamente sabe algo antes de eu fazer isso.

  • Por isso, o Google Play tem novos requisitos de qualidade no limite de desempenho.

  • O Android e o Google Play querem que os usuários usem apps de boa qualidade.

  • Portanto, esse limite também é algo que tentamos trazer de melhor para o usuário final.

  • Em outras palavras, "Ei, quando seu app estiver em execução em segundo plano ou em uma plataforma observável em primeiro plano, o percentil 90 do uso da memória não pode ultrapassar esse limite.

  • Caso contrário, seu app pode ter uma visibilidade reduzida na Google Play Store ou algo assim.

  • A aplicação vai começar em fevereiro de 2027 para apps e jogos.

  • Ultrapassar esse limite afeta diretamente a avaliação de integridade do app.

  • Por isso, acesse o Google Play Console e verifique: "Qual é meu percentil 90 hoje?"

  • E faça a melhoria antes do prazo.

  • Esta é a animação.

  • "Se você acessar o Google Play Console, vai encontrar o Android vitals.

  • Há uma parte da memória.

  • E você pode ver o percentil 90.

  • E se você clicar nele, sim, poderá ver ao longo do tempo a média do consumo de memória.

  • Também há diferentes níveis por RAM, formato, densidade de tela e versão do SO, que ajudam a isolar o gargalo de memória em um dispositivo básico específico ou em versões específicas do app.

  • Agora, você pode dizer: "Obrigado por me avisar que meu app está acima do limite.

  • Mas, sério, não sei o que está errado.

  • Você pode ajudar?"

  • Sim, por isso estou aqui para ajudar você.

  • Vou tentar facilitar para você. Por exemplo, se você estiver assistindo TV e ela ficar sem vídeo ou áudio, o que você vai fazer?

  • Você pega o manual da TV, vai até as últimas páginas, que geralmente são o guia de solução de problemas, e lê: "Se a TV não tiver som, tente este botão ou aquele botão".

  • Sim, da mesma forma, hoje identifiquei alguns problemas comuns.

  • E, se tudo der certo, este será seu guia de solução de problemas.

  • Normalmente, quando lemos o manual da TV, devemos começar pela página um, que geralmente é o kit de ferramentas de diagnóstico para ajudar você a entender como a TV funciona.

  • Mas agora que sei que isso é menos interessante, permita-me começar pela solução de problemas e voltar a isso mais tarde.

  • E, para ser sincero, é assim que eu leio o manual da TV.

  • Para descobrir o problema mais frequente de alocação de memória de bitmap no ecossistema Android, pesquisei cerca de 500 apps conhecidos no Android.

  • Eu realmente executo o app, inicio, faço login, acesso páginas diferentes, vou fundo e volto.

  • E testo o app em diferentes RAMs e dispositivos, como um dispositivo Android Go com 2 GB de memória, um de entrada com 4 GB, um intermediário com 4 GB, um de 6 GB a 8 GB ou um dobrável premium com uma tela bem grande, de ótima qualidade e 16 GB de memória.

  • Medimos o uso da memória nesse dispositivo em diferentes status do app, capturando o heap dump e usando diferentes ferramentas, incluindo a habilidade de IA do Perfetto, para analisar a alocação e identificar antipadrões comuns, ou seja, os erros comuns que cometemos em grande escala.

  • E é assim que a memória muda em diferentes estados de atividade.

  • Com essa análise, identificamos mais de 15 problemas comuns, que vão de imagens grandes demais a caches de segundo plano crescentes.

  • Esse é o estudo de caso principal que vamos compartilhar e analisar hoje.

Estudos de caso: dimensionamento e decodificação de bitmaps

  • Fung Lam

    Sim, todo o estudo de caso que estou compartilhando agora é sobre a pesquisa de 500 apps.

  • Como vocês estão aqui, acho que todos nós amamos o Android, certo?

  • Todos nós amamos o Android.

  • Digamos que um de nós também adore o Android, mas não seja especialista em desenvolvimento para Android.

  • Então, ele cria um app Android para pedir bonecos do Android.

  • Mas há alguns erros.

  • Vamos conversar. Talvez esse seja o primeiro erro.

  • "Na página do produto, temos um layout de duas colunas que mostra diferentes bonecos do Android.

  • Para cada coluna, a imagem tem cerca de 400 x 400 pixels.

  • No entanto, o app está baixando uma imagem do servidor, não importa o tamanho da tela.

  • Ele está baixando uma imagem de 3.000 x 3.000.

  • Sim, porque, como desenvolvedor, "O Android é tão bonito, tão incrível.

  • Quero mostrar o melhor para o usuário."

  • Mas, na verdade, não, porque, tipo, ela é limitada pela dimensão da visualização de imagem.

  • Portanto, mesmo que você faça o download da imagem grande, ela não será mostrada da melhor forma para o usuário.

  • Além disso, essa imagem grande causou um uso extra de memória.

  • E mesmo que façamos uma redução de resolução, o processo também usa mais memória.

  • Como podemos ajudar esse desenvolvedor a corrigir o problema?

  • Se você conhece a CDN, sabe que ela pode lidar com algumas operações de imagem, como cortar, redimensionar, girar, aplicar filtros e transformar uma imagem quadrada em redonda.

  • Por solicitação HTTP, a CDN pode lidar com algo.

  • Portanto, mesmo que o servidor hospede apenas uma imagem, você pode usar a CDN para enviar uma imagem redimensionada ao dispositivo.

  • Então, número 1, sim, quando você solicitar a imagem, tente usar a CDN para que ela seja solicitada no tamanho da ImageView.

  • Mas, se não for possível, por exemplo, se às vezes ela for um pouco maior que a ImageView, lembre-se de reduzir a resolução para redimensioná-la no código.

  • Assim, quando ela é carregada na memória, é uma imagem menor, não a resolução completa.

  • Confira um exemplo de código para a Coil.

    // 1. Ask CDN for the exact UI card dimensions (e.g. 540x675)
    val sizedUrl = "$cdnBaseUrl?w=$cardWidthPx&h=$cardHeightPx&fmt=webp"
    
    // 2. Enforce exact downsampling + Hardware Bitmap config
    AsyncImage(
        model = ImageRequest.Builder(LocalContext.current)
            .data(sizedUrl)
            .size(cardWidthPx, cardHeightPx) // Never use Size.ORIGINAL!
            .precision(Precision.EXACT)      // Downsample to exact card size
            .bitmapConfig(Bitmap.Config.HARDWARE)
            .build(),
        contentDescription = product.title
    )
  • Você pode transmitir o tamanho como o texto em amarelo.

  • Sim, você pode transmitir o tamanho como HTTP em uma solicitação HTTP.

  • E aqui, no código destacado, você consegue ver o destaque? Sim, não use Size.ORIGINAL.

  • Sim, use a largura e a altura para que sejam transmitidas como parâmetro.

  • Use a precisão para reduzir a resolução da imagem para o tamanho exato.

  • Se possível, use o Bitmap.Config.HARDWARE, que também usa menos memória, já que a imagem será armazenada diretamente na VRAM.

  • Da mesma forma, neste app, digamos que eles lancem uma nova versão de colecionáveis do Android. Há uma imagem longa que o usuário pode deslizar para a esquerda e para a direita para ver diferentes designs da nova figura do Android.

  • Mas há outro problema aqui.

  • No caso da imagem longa, grande parte dela fica fora da tela, ou seja, o usuário não consegue ver, mas a imagem é carregada na memória.

  • Podemos fazer melhor?

  • Sim.

  • Podemos dividi-la em uma imagem menor e mostrar apenas a imagem que será carregada na tela.

  • As que estiverem além da tela não serão carregadas ou serão descarregadas.

  • Portanto, ele vai usar menos memória.

  • Da mesma forma, neste caso, ele economizou cerca de 60% de memória.

  • Claro que, se você cortar em um retângulo pequeno, vai economizar mais.

  • Mas, da mesma forma, se um usuário quiser fornecer um recurso para o usuário, ele poderá aumentar o zoom para ver todos os detalhes do boneco do Android. Um erro comum é carregar, novamente, uma imagem muito grande para que ela possa ser ampliada e ampliada novamente.

  • Mas não, você não precisa fazer isso.

  • Uma maneira melhor é quando o usuário aumenta o zoom na imagem de baixa resolução original já carregada e, quando o usuário para de fazer o gesto de pinça, você calcula a coordenada e carrega a resolução completa, ou seja, carrega apenas essa área, uma área calculada da imagem de alta resolução.

  • Portanto, apenas essa área visível será carregada na memória.

  • Como fazer isso?

  • Ah, existe uma API chamada BitmapRegionDecoder, que ajuda a carregar uma região específica de uma imagem.

  • E, claro, também há algumas ferramentas e bibliotecas que podem ajudar você.

  • Você pode pesquisar no GitHub.

  • Alguns apps de quadrinhos ou mapas já usam isso.

  • Você pode usá-los como referência.

  • Além disso, somos humanos e cometemos erros.

  • A IA também comete erros.

  • Para evitar erros, também podemos adicionar um limite ao carregar imagens.

  • Por exemplo, neste caso, eu poderia dizer: "Vamos usar o tamanho da janela ou a dimensão da tela como limite e colocar isso em uma função min, min da largura da solicitação ou da largura do limite".

    // Query active window bounds instead of hardcoding 1080 / 1920
    val bounds = WindowMetricsCalculator.getOrCreate()
        .computeCurrentWindowMetrics(activity).bounds
    
    // Clamp target dimensions so Bitmap NEVER exceeds the active window
    val safeWidth  = minOf(requestedWidthPx,  bounds.width())
    val safeHeight = minOf(requestedHeightPx, bounds.height())
    
    val bitmap = Bitmap.createBitmap(safeWidth, safeHeight, config)
  • Então, com essa função, sempre que eu carregar uma imagem, ela nunca vai ultrapassar o limite.

  • Portanto, não será carregada uma imagem maior do que a resolução da tela.

  • Lembre-se de decodificar apenas a região visível da imagem que vai salvar sua memória.

Gerenciar memória, cópias e cache

  • Fung Lam

    E, de novo, dizer: "Ei, esse app não é de um desenvolvedor.

  • Na verdade, é para vários colaboradores, e eles podem estar usando ferramentas diferentes.

  • Por exemplo, parte disso é usar, ah, desculpe.

  • Vou começar de novo.

  • O mesmo vale para este app.

  • Na página do produto, tenho uma lista de produtos e vi essa imagem, então adicionei ao carrinho de compras.

  • Alguns minutos depois, vou para o checkout e, olha só, a mesma imagem aparece de novo.

  • Mas será que é a mesma imagem ou são dois objetos diferentes na memória?

  • Sim, um dos erros comuns que encontramos é, por exemplo, o app carregar muitas imagens.

  • No entanto, eles também estão tendo muitas imagens duplicadas na memória.

  • Se você estiver usando o carregador de imagens, sim, é possível transmitir uma chave para fazer o armazenamento em cache.

  • Assim, quando a mesma imagem for usada, você poderá usar o objeto Bitmap já alocado em vez de criar um novo.

  • No Android Studio, você pode usar o Memory Profiler para descobrir se o app tem bitmaps duplicados.

  • Abordei esse assunto na minha sessão do Google I/O, então você pode voltar ao YouTube para assistir o vídeo.

  • Se você não estiver usando o Android Studio, também há comandos adb para capturar o heap dump. Depois, você pode usar a linguagem SQL do gráfico de heap do Perfetto para consultar se o app tem um Bitmap duplicado.

  • Este é um exemplo de código no Coil, que mostra como adicionar uma chave para o armazenamento em cache de bitmap.

    // Strip volatile query tokens (?from=list / ?sig=...) for cache key
    val canonicalKey = MemoryCache.Key(productUrl.substringBefore("?"))
    
    // On Checkout Screen: reuse the 200x200 Bitmap already loaded in the List!
    AsyncImage(
        model = ImageRequest.Builder(context)
            .data(productUrl)
            .placeholderMemoryCacheKey(canonicalKey) // Instant zero-decode reuse!
            .memoryCacheKey(canonicalKey)
            .diskCacheKey(productUrl.substringBefore("?"))
            .build(),
        contentDescription = product.title
    )
  • E agora, sim.

  • Volto ao exemplo anterior.

  • Olá, meu app tem um desenvolvedor diferente.

  • Algumas partes usam visualizações, então o Glide é usado como carregador de imagens.

  • E alguns deles estão usando o Coil porque usam o Compose.

  • Então, o carregador de imagens que você mencionou ajuda a fazer o armazenamento em cache. Há algum problema em ter mais de um carregador de imagens?

  • Com certeza, algo está errado.

  • Por padrão, o carregador de imagens usa X% do tamanho da memória heap como o tamanho padrão do cache.

  • Se você tiver um carregador de imagens, ele vai usar X% da memória.

  • Se você tiver dois carregadores de imagens, o uso de memória será o dobro.

  • Se você tiver três carregadores de imagens, será 3X.

  • E se o carregador de imagens não compartilhar o mesmo pool, vai acontecer a mesma coisa.

  • Como na página do produto, carrego uma imagem usando o Glide e, na página de finalização da compra, a mesma imagem é carregada usando o Coil.

  • Ainda assim, haverá duas imagens na memória.

  • Isso não é bom.

  • Sim, para corrigir isso, o ideal é usar apenas um carregador de imagens ou um pool compartilhado para o armazenamento em cache.

  • Mas e se isso for, tipo, muito trabalho de engenharia, eu não conseguiria fazer.

  • Uma correção mais fácil seria medir qual porcentagem da imagem será processada por cada carregador e definir um peso para cada um, como neste código.

    val totalBudgetBytes = computeAdaptiveCacheBytes(context)
    val numberOfLoaders = 2 // e.g., Team A (Glide) + Team B (Coil)
    
    // Approach A (With Stats): Weight by image traffic (70% Team A / 30% Team B)
    val loaderABudget = (totalBudgetBytes * 0.70).toInt()
    val loaderBBudget = (totalBudgetBytes * 0.30).toInt()
    
    // Approach B (Simple Fix without stats): Divide by number of loaders!
    val simpleSplitBudget = totalBudgetBytes / numberOfLoaders
    
    MemoryCache.Builder()
        .maxSizeBytes(simpleSplitBudget)
        .build()
  • Eu poderia definir loaderABudget como 70% e loaderBBudget como 30%. Essa é outra maneira.

  • Se você realmente não sabe quantas imagens serão processadas por cada carregador, ainda assim é melhor dividir.

  • "Tenho dois carregadores de imagem, divida por 2; três carregadores de imagem, divida por 3", em vez de usar o valor padrão, porque isso vai sobrecarregar a memória.

  • E digamos que, na página do produto, esperamos que o usuário role para ver mais do produto.

  • Então, normalmente, podemos armazenar em cache cerca de 50 imagens ou 50 MB de imagens.

  • Mas e se o app for para o segundo plano?

  • Se o app estiver em segundo plano, o usuário pode não estar navegando.

  • Se o usuário não estiver navegando, ainda devo manter essa quantidade de imagens em cache na memória?

  • Não, vamos soltá-lo.

  • Sim, precisamos respeitar o callback onTrimMemory.

  • Sim, muitos apps não implementam ou não respeitam o callback onTrimMemory.

  • Para o callback onTrimMemory, há dois sinais que precisamos considerar.

  • Um deles é UI_HIDDEN, que é como se o usuário tivesse pressionado o botão home, mas não imediatamente após pressionar o botão home, é um período após pressionar o botão home, o que significa: "Ah, seu app está em um estado em que não há uma IU visível para o usuário".

  • E podemos esperar: "Ei, o usuário não está usando seu app.

  • Libere os recursos para o SO."

  • Portanto, precisamos reduzir o tamanho do cache.

  • O outro é TRIM_MEMORY_BACKGROUND, o que significa que o app já está em segundo plano há muito tempo e o SO realmente precisa de alguns recursos.

  • Nesse estágio, você precisa liberar o máximo de memória possível, quase 100%.

  • Ao fazer isso, você também reduz a chance de o app ser encerrado pelo SO.

  • Porque, tipo, "Ei, eles querem memória, você dá a eles, e o SO lida com isso".

  • Se não, o SO não tem escolha, ele precisa fazer algo.

Como evitar o crescimento da memória em segundo plano

  • Fung Lam

    Outro erro comum que vejo é: "Acabei de mencionar que, se o app for para o segundo plano, ele precisa usar menos memória".

  • No entanto, vi apps usando mais memória em segundo plano.

  • Por quê?

  • Por isso eu pesquiso.

  • Tentei descobrir o motivo.

  • E um dos motivos é: "No app deles, há uma animação ou um carrossel.

  • Por exemplo, eles baixam uma nova imagem a cada 15 segundos e mostram uma nova imagem ao usuário."

  • No entanto, o desenvolvedor não interrompeu a animação quando o app vai para o segundo plano.

  • Portanto, mesmo que o app esteja em segundo plano, o código ainda está em execução, recebendo uma nova imagem, decodificando-a e usando mais memória.

  • Isso é algo que o app não deveria fazer.

  • Isso mesmo.

  • Precisamos interromper todas as animações, o armazenamento em cache ou o trabalho não relacionado quando o app vai para o segundo plano.

  • Se você conhece o ciclo de vida da atividade, sabe que existem os eventos onPause e onStop. Implemente onStop, onPause e onDestroyView para interromper a animação, as solicitações de pull de imagens ou outras coisas relacionadas ao trabalho que não precisam da atenção do usuário.

    override fun onStop() {
        super.onStop()
        // 1. Stop animations so they don't decode next frames in background!
        bannerAnimationView.cancelAnimation()
        animatedImageDrawable?.stop()
    
        // 2. Cancel next-image buffering / prefetch queues!
        nextImagesBufferingJob?.cancel()
        Glide.with(this).pauseRequests()
    }
    
    override fun onDestroyView() {
        super.onDestroyView()
        bannerAnimationView.clearAnimation() // Release resources!
    }

O framework dos 3Rs para a saúde da memória

  • Fung Lam

    Sei que já falei sobre muitos casos com você, mas eu disse: "Tenho mais de 15 casos".

  • Se eu compartilhar todos os casos com você, talvez você se esqueça de tudo.

  • Quando você voltar para casa, vai pesquisar no Google: "Como corrigir meus problemas de memória?"

  • Por isso, vou facilitar para você.

  • Isso mesmo.

  • Vou associá-los a um framework de 3 Rs muito conhecido para ajudar você a memorizá-los.

  • Acho que todo mundo já ouviu falar do framework dos 3 Rs.

  • A primeira é a redução, ou seja, como reduzir a memória ao carregar uma imagem.

  • Claro, como mencionei, podemos reduzir a resolução, obter a dimensão certa da imagem quando a recebemos do servidor.

  • Otimizamos a profundidade de cor ou usamos o bitmap de hardware, se possível.

  • Se você não estiver usando uma CDN, tente usar uma.

  • Assim, quando recebemos a imagem do servidor, ela é a mais adequada para o tamanho.

  • Reutilização: usamos o cache para que o carregamento da imagem na memória seja reutilizado e não tenhamos imagens de bitmap duplicadas na memória.

  • E, claro, tente não ter vários carregadores de imagem no seu código, porque isso é menos eficiente.

  • Reduzir, reutilizar e qual é o terceiro?

  • Você sabe?

  • Recicle.

  • Quer tentar novamente?

  • Reciclar, sim, reciclar.

  • Sim, aprendemos a reduzir, reutilizar e reciclar na escola primária, embora não aprendamos a programar na escola primária.

  • Sim, reciclar, sim.

  • Sim, é sobre como liberar a memória para que outro aplicativo ou o SO possa usá-la.

  • Há coisas como reciclagem.

  • Primeiro, mencionei que há um evento onTrimMemory, que indica que há dois sinais que o SO vai enviar quando a IU estiver oculta ou quando o SO realmente precisar de recursos.

  • Sim, tente lidar com esse pedido.

  • E, claro, se você tiver uma animação, um carregador de imagens ou algum outro trabalho em primeiro plano, interrompa-o quando houver um evento onPause, onStop.

  • Reduza, reutilize e recicle.

  • Fácil de lembrar, não é?

Kit de ferramentas de diagnóstico para desenvolvedores e análise de IA

  • Fung Lam

    Agora vamos voltar para a parte menos interessante do manual da TV.

  • Como usamos diferentes kits de ferramentas para detectar e medir esse problema de memória de forma eficaz?

  • A primeira é o Google Play Console, o Android vitals e a API Reporting.

  • Sim, confira o percentil 90 no Google Play Console.

  • Onde você está agora?

  • Ou use a API Reporting se você não acessar a página da Web.

  • Você ainda pode usar a API para receber o valor.

  • Sim, verifique se o limite é de 200 MB e 400 MB.

  • Verifique se você não ultrapassou o limite.

  • Também há comandos ADB para ajudar você, como capturar o heap dump ou enviar um evento para simular, por exemplo, que o app está em um estágio UI_HIDDEN.

    adb shell dumpsys meminfo <pkg>  # watch Native Heap!
    adb shell am send-trim-memory <pkg> UI_HIDDEN
  • Se você estiver usando o Android Studio, ótimo.

  • No entanto, ele sempre será um bom amigo para você.

  • Você pode usar o criador de perfil do Android Studio para capturar o heap dump e ter uma interface para ver o uso real da memória e a alocação de memória.

  • E, claro, falamos sobre IA com o Gemini no Android Studio ou outras habilidades disponíveis do Android.

  • Ele ajuda a analisar o heap dump e usa traces SQL do Perfetto para ajudar a encontrar o Bitmap, que normalmente informa quem é o proprietário do cache.

  • Minha imagem é carregada pelo Glide ou pelo Coil?

  • Há algum outro vazamento de memória no meu aplicativo?

  • Sei que alguns de vocês vão dizer: "Na verdade, não uso mais o IDE.

  • Sou engenheiro de comandos.

  • Eu uso, sim, só envio um comando com o agente de IA para fazer o código."

  • Sim, eu sei.

  • Também faço um pouco de vibe coding no meu tempo livre.

  • Então, estou compartilhando isso com você como um exemplo.

  • Sim, isso é só parte do comando que uso para minha pesquisa de 500 apps.

  • Sim, só uma parte.

  • Você sabe que a tela não é grande.

  • Se eu colocar mais, você mal vai conseguir ver.

  • Mas isso dá uma ideia de como usar a IA para automatizar, capturar o uso da memória, incluindo o heap dump, e analisar e fornecer descobertas para você.

  • E sim, eu poderia dizer aqui, em mais uma frase, "Por favor, forneça também a correção para meu código".

  • Sim, espero que isso possa reduzir seus medos, tipo, "Ei, a memória é um domínio grande.

  • Não estudei sistemas operacionais nem arquitetura de sistemas de computadores em ciência da computação na faculdade.

  • Mas, na verdade, isso não é tão assustador, não é tão difícil."

  • Sim, isso facilita a análise do seu app.

  • Tente de novo.

  • Mas é claro que a IA vai cometer erros, então o conhecimento fundamental sobre como o uso da memória também é como o SO aloca memória ainda é importante.

  • Por isso, assista à minha palestra e ao nosso vídeo no YouTube.

Principais conclusões e resumo

  • Fung Lam

    Para recapitular, temos algumas conclusões.

  • Primeiro, reduzir.

  • Verifique se o tamanho da imagem está correto.

  • Você divide a imagem em imagens menores para não carregar tudo na memória, apenas a imagem que está visível para o usuário.

  • Use o hardware bitmapConfig ou RGB_565 sempre que possível.

  • Reutilize. Tenha uma estratégia de cache e use o carregador de imagens para reduzir o número de bitmaps duplicados na memória.

  • E, claro, não use várias imagens de bitmap, mesmo que você tente substituir o tamanho de cache padrão desses carregadores de imagem.

  • E não reutilizamos nem reciclamos.

  • Isso mesmo.

  • O gerenciamento de memória do app precisa se adaptar ao estado dele.

  • Se a IU estiver oculta, sim, você deve usar menos memória.

  • Se o SO realmente pedir para você liberar a memória, libere o máximo que puder.

  • E pare de consumir os recursos, sejam eles de bitmap, energia ou computacionais.

  • Sim, interrompa o timer do banner, a animação e o pooling de imagens.

  • Por fim, monitore.

  • Acesse o Google Play Console regularmente.

  • Faça disso uma prática.

  • Sim, isso vai ajudar você a entender qual é o status atual do 90º percentil do uso da memória do seu app em toda a população de usuários.

  • Isso também ajuda a identificar problemas com antecedência.

  • Isso mesmo.

  • Para criar um perfil localmente, use o Memory Profiler do Android Studio.

  • Também temos alguns comandos do ADB.

  • Também temos uma skill para Android.

  • Mas se você quiser ver as estatísticas de usuários reais, o Google Play Console é o lugar certo.

  • E também há a API Google Play Reporting para você acessar usando a API sem precisar fazer login no Play Console.

  • Como sempre, para saber mais, acesse developer.android.com/memory.

  • Reduza, reutilize e recicle com isso.

  • Podemos criar um ecossistema Android melhor para superar os desafios que estamos enfrentando.

  • Agradecemos a atenção e boa codificação.

  • Em breve, vou atender vocês lá fora. Se tiverem dúvidas, fiquem à vontade para perguntar.

  • E o próximo palestrante, bem, temos algum tempo, então aguarde o próximo palestrante subir ao palco. Muito obrigado.

Código

Snippets de código

Snippet do Kotlin aos 11:50 em "Estudos de caso: dimensionamento e decodificação de bitmaps"
// 1. Ask CDN for the exact UI card dimensions (e.g. 540x675)
val sizedUrl = "$cdnBaseUrl?w=$cardWidthPx&h=$cardHeightPx&fmt=webp"

// 2. Enforce exact downsampling + Hardware Bitmap config
AsyncImage(
    model = ImageRequest.Builder(LocalContext.current)
        .data(sizedUrl)
        .size(cardWidthPx, cardHeightPx) // Never use Size.ORIGINAL!
        .precision(Precision.EXACT)      // Downsample to exact card size
        .bitmapConfig(Bitmap.Config.HARDWARE)
        .build(),
    contentDescription = product.title
)
Snippet do Kotlin aos 14:45 em "Estudos de caso: dimensionamento e decodificação de bitmaps"
// Query active window bounds instead of hardcoding 1080 / 1920
val bounds = WindowMetricsCalculator.getOrCreate()
    .computeCurrentWindowMetrics(activity).bounds

// Clamp target dimensions so Bitmap NEVER exceeds the active window
val safeWidth  = minOf(requestedWidthPx,  bounds.width())
val safeHeight = minOf(requestedHeightPx, bounds.height())

val bitmap = Bitmap.createBitmap(safeWidth, safeHeight, config)
Snippet do Kotlin aos 16:51 em "Como gerenciar memória, duplicatas e cache"
// Strip volatile query tokens (?from=list / ?sig=...) for cache key
val canonicalKey = MemoryCache.Key(productUrl.substringBefore("?"))

// On Checkout Screen: reuse the 200x200 Bitmap already loaded in the List!
AsyncImage(
    model = ImageRequest.Builder(context)
        .data(productUrl)
        .placeholderMemoryCacheKey(canonicalKey) // Instant zero-decode reuse!
        .memoryCacheKey(canonicalKey)
        .diskCacheKey(productUrl.substringBefore("?"))
        .build(),
    contentDescription = product.title
)
Snippet do Kotlin aos 18:17 em "Como gerenciar memória, duplicatas e cache"
val totalBudgetBytes = computeAdaptiveCacheBytes(context)
val numberOfLoaders = 2 // e.g., Team A (Glide) + Team B (Coil)

// Approach A (With Stats): Weight by image traffic (70% Team A / 30% Team B)
val loaderABudget = (totalBudgetBytes * 0.70).toInt()
val loaderBBudget = (totalBudgetBytes * 0.30).toInt()

// Approach B (Simple Fix without stats): Divide by number of loaders!
val simpleSplitBudget = totalBudgetBytes / numberOfLoaders

MemoryCache.Builder()
    .maxSizeBytes(simpleSplitBudget)
    .build()
Snippet do Kotlin aos 22:05 em "Como evitar o crescimento da memória em segundo plano"
override fun onStop() {
    super.onStop()
    // 1. Stop animations so they don't decode next frames in background!
    bannerAnimationView.cancelAnimation()
    animatedImageDrawable?.stop()

    // 2. Cancel next-image buffering / prefetch queues!
    nextImagesBufferingJob?.cancel()
    Glide.with(this).pauseRequests()
}

override fun onDestroyView() {
    super.onDestroyView()
    bannerAnimationView.clearAnimation() // Release resources!
}
Snippet de shell em 25:17 em "Kit de ferramentas de diagnóstico para desenvolvedores e análise de IA"
adb shell dumpsys meminfo <pkg>  # watch Native Heap!
adb shell am send-trim-memory <pkg> UI_HIDDEN