Qual é a quantidade máxima de RAM que um aplicativo pode usar?

145

Estou bastante curioso sobre esta questão relativa à gerenciamento de memória do sistema operacional Android, por isso espero uma resposta bastante detalhada sobre esse tópico.

O que eu gostaria de saber:

  • Qual é a quantidade máxima de memória (em megabytes / como porcentagem da RAM total) que um aplicativo Android (que não é um aplicativo do sistema) pode usar?
  • Existem diferenças entre as versões do Android ?
  • Existem diferenças quanto ao fabricante do dispositivo?

E o mais importante:

  • O que é considerado / do que depende quando o sistema determina a quantidade de RAM que um aplicativo pode usar em tempo de execução (assumindo que o máximo de memória por aplicativo não seja um número estático)?

O que ouvi até agora (até 2013):

  • Os primeiros dispositivos Android tinham um limite de 16 MB por aplicativo
  • Posteriormente, esse limite aumentou para 24 MB ou 32 MB

O que me deixa muito curioso:

Esses dois limites são muito baixos.

Acabei de baixar o Android Task Manager para verificar a RAM dos meus dispositivos. O que eu notei é que existem aplicativos usando cerca de 40 a 50 megabytes de RAM, o que é obvioulsy mais do que o uso máximo de RAM mencionado, digamos 32 MB. Então, como o Android determina quanta RAM um aplicativo pode usar? Como é possível que os aplicativos excedam esse limite?

Além disso, notei que alguns aplicativos meus travam (eliminados pelo sistema?) Com uma OutOfMemoryException ao usar cerca de 30 a 40 megabytes. Por outro lado, tenho aplicativos em execução no meu telefone usando 100 MB ou mais depois de algum tempo (provavelmente devido a vazamentos de memória) que não travam ou são mortos. Portanto , obviamente, também depende do próprio aplicativo quando se trata de determinar quanta RAM pode ser poupada. Como isso é possível? (Realizei meus testes com um HTC One S com 768 MB de RAM)

Disclaimer: Eu não sou afiliado com o aplicativo Android Task Manager de qualquer forma.

Philipp Jahoda
fonte

Respostas:

119

Qual é a quantidade máxima de memória (em megabytes / como porcentagem da RAM total) que um aplicativo Android (que não é um aplicativo do sistema) pode usar?

Isso varia de acordo com o dispositivo. getMemoryClass()onActivityManager fornecerá o valor para o dispositivo em que seu código está sendo executado.

Existem diferenças entre as versões do Android?

Sim, na medida em que os requisitos do sistema operacional aumentaram ao longo dos anos e os dispositivos precisam ser ajustados para corresponder.

Existem diferenças em relação ao fabricante do dispositivo?

Sim, na medida em que os fabricantes fabricam dispositivos, e o tamanho varia de acordo com o dispositivo.

Quais "fatores colaterais" são levados em consideração quando se trata de determinar quanta RAM um aplicativo pode usar?

Não tenho idéia do que "fatores colaterais" significam.

Os primeiros dispositivos tinham um limite de 16 MB por aplicativo; Dispositivos posteriores aumentaram para 24 MB ou 32 MB

Isso é certo. A resolução da tela é um determinante significativo, pois resoluções maiores significam bitmaps maiores e, portanto, tablets e telefones de alta resolução tenderão a ter valores mais altos ainda. Por exemplo, você verá dispositivos com 48 MB de pilha e não ficaria surpreso se houver valores maiores que isso.

Como é possível que os aplicativos excedam esse limite?

Você assume que o autor desse aplicativo sabe o que está fazendo. Considerando que o uso da memória de um aplicativo é difícil para um engenheiro do Android determinar , eu não assumiria que o aplicativo em questão esteja necessariamente fornecendo resultados particularmente precisos.

Dito isto, o código nativo (NDK) não está sujeito ao limite de heap. E, desde o Android 3.0, os aplicativos podem solicitar uma "grande pilha", geralmente na faixa de centenas de MB, mas isso é considerado uma má forma para a maioria dos aplicativos.

Além disso, notei que alguns aplicativos meus travam com uma OutOfMemoryException ao usar cerca de 30 a 40 megabytes.

Lembre-se de que o coletor de lixo do Android não é um coletor de lixo compacto. A exceção realmente deveria ser CouldNotFindSufficientlyLargeBlockOfMemoryException, mas isso provavelmente foi considerado muito prolixo. OutOfMemoryExceptionsignifica que você não pode alocar o bloco solicitado , não esgotou completamente o heap.

CommonsWare
fonte
Não consigo entender sobre a tabela, tenho o Xperia X mobile com resolução de 1080 x 1920 que é de alta resolução e outro dispositivo Samsung Tab 4 com resolução de 800 x 1280; 3GB de RAM e guia vem com 1.5GB de RAM, então o tablet ocupa uma ram grande por causa da tela grande?
Rahul Mandaliya
@RahulMandaliya: Sinto muito, mas não entendo sua preocupação ou o que ela tem a ver com esta pergunta. Você pode abrir uma pergunta separada sobre estouro de pilha, na qual explica detalhadamente qual é a sua preocupação.
CommonsWare
15

É o fim de 2018, então as coisas mudaram.

Primeiro: execute o aplicativo e abra a guia Android Profiler no Android Studio. Você verá quanta memória consome, ficará surpreso, mas ele pode alocar muita RAM.

Também aqui está um excelente artigo em documentos oficiais, com instruções detalhadas sobre como usar o Memory Profiler, que pode fornecer uma visão detalhada do gerenciamento de memória.

Mas na maioria dos casos, seu Android Profiler regular será suficiente para você.

insira a descrição da imagem aqui

Normalmente, um aplicativo começa com 50Mb de alocação de RAM, mas salta instantaneamente até 90Mb quando você começa a carregar algumas fotos na memória. Quando você abre o Activity com um ViewPager com fotos pré-carregadas (3,5Mb cada), você pode obter 190Mb facilmente em segundos.

Mas isso não significa que você tenha problemas com o gerenciamento de memória.

O melhor conselho que posso dar é seguir as diretrizes e práticas recomendadas, usar as principais bibliotecas para carregamento de imagens (Glide, Picasso) e você ficará bem.


Mas, se você precisar adaptar algo e realmente precisar saber quanta memória pode alocar manualmente, poderá obter memória livre total e calcular uma parte pré-determinada (em%) dela. No meu caso, eu precisava armazenar em cache as fotos descriptografadas na memória para não precisar descriptografá-las sempre que o usuário passar pela lista.

Para esse fim, você pode usar a classe LruCache pronta para usar . É uma classe de cache que rastreia automaticamente a quantidade de memória que seus objetos alocam (ou o número de instâncias) e remove a mais antiga para manter as recentes pelo histórico de uso. Aqui está um ótimo tutorial sobre como usá-lo.

No meu caso, criei 2 instâncias de caches: para polegares e anexos. Tornou-os estáticos com acesso único, para que estejam disponíveis globalmente em todo o aplicativo.

classe de cache:

public class BitmapLruCache extends LruCache<Uri, byte[]> {

    private static final float CACHE_PART_FOR_THUMBS_PRC = 0.01f; // 1% (Nexus 5X - 5Mb)
    private static final float CACHE_PART_FOR_ATTACHMENTS_PRC = 0.03f;// 3% (Nexus 5X - 16Mb)
    private static BitmapLruCache thumbCacheInstance;
    private static BitmapLruCache attachmentCacheInstance;

public static synchronized BitmapLruCache getDecryptedThumbCacheInstance() {
    if (thumbCacheInstance == null) {

        int cacheSize = getCacheSize(CACHE_PART_FOR_THUMBS_PRC);
    //L.log("creating BitmapLruCache for Thumb with size: " + cacheSize + " bytes");
        thumbCacheInstance = new BitmapLruCache(cacheSize);
        return thumbCacheInstance;
    } else {
        return thumbCacheInstance;
    }
}

public static synchronized BitmapLruCache getDecryptedAttachmentCacheInstance() {
    if (attachmentCacheInstance == null) {

        int cacheSize = getCacheSize(CACHE_PART_FOR_ATTACHMENTS_PRC);
    //            L.log("creating BitmapLruCache for Attachment with size: " + cacheSize + " bytes");
        attachmentCacheInstance = new BitmapLruCache(cacheSize);
        return attachmentCacheInstance;
    } else {
        return attachmentCacheInstance;
    }
}

private BitmapLruCache(int maxSize) {
    super(maxSize);
}

public void addBitmap(Uri uri, byte[] bitmapBytes) {
    if (get(uri) == null && bitmapBytes != null)
        put(uri, bitmapBytes);
}

public byte[] getBitmap(Uri uri) {
    return get(uri);
}


@Override
protected int sizeOf(Uri uri, byte[] bitmapBytes) {
    // The cache size will be measured in bytes rather than number of items.
    return bitmapBytes.length;
}
}

É assim que eu calculo a RAM livre disponível e quanto posso extrair dela:

private static int getCacheSize(float partOfTotalFreeMemoryToUseAsCache){
    final long maxMemory = Runtime.getRuntime().maxMemory();
    //Use ... of available memory for List Notes thumb cache
    return (int) (maxMemory * partOfTotalFreeMemoryToUseAsCache);
}

E é assim que eu o uso nos adaptadores para obter a imagem em cache:

byte[] decryptedThumbnail = BitmapLruCache.getDecryptedThumbCacheInstance().getBitmap(thumbUri);

e como eu o configuro no cache no thread de segundo plano (AsyncTask regular):

BitmapLruCache.getDecryptedThumbCacheInstance().addBitmap(thumbUri, thumbBytes); 

Meu aplicativo tem como alvo a API 19+, para que os dispositivos não sejam antigos e essas partes da RAM disponível sejam boas o suficiente para cache no meu caso (1% e 3%).

Curiosidade: o Android não possui APIs ou outros hacks para obter a quantidade de memória alocada ao seu aplicativo, é calculado em tempo real com base em vários fatores.


PS: Estou usando um campo de classe estática para armazenar um cache, mas de acordo com as diretrizes mais recentes do Android, é recomendável usar o componente de arquitetura ViewModel para esse fim.

Kirill Karmazin
fonte