Detectar o tamanho da pilha de aplicativos no Android

145

Como você detecta programaticamente o tamanho de heap do aplicativo disponível para um aplicativo Android?

Ouvi dizer que há uma função que faz isso em versões posteriores do SDK. De qualquer forma, estou procurando uma solução que funcione para a versão 1.5 ou superior.

hpique
fonte
2
Veja também: stackoverflow.com/questions/1518154/…
Josh Lee

Respostas:

453

Há duas maneiras de pensar sobre sua frase "tamanho de heap do aplicativo disponível":

  1. Quanto heap meu aplicativo pode usar antes que um erro grave seja acionado? E

  2. Quanto heap meu aplicativo deve usar, dadas as restrições da versão do sistema operacional Android e o hardware do dispositivo do usuário?

Existe um método diferente para determinar cada uma das opções acima.

Para o item 1 acima: maxMemory()

que pode ser invocado (por exemplo, no onCreate()método da sua atividade principal ) da seguinte maneira:

Runtime rt = Runtime.getRuntime();
long maxMemory = rt.maxMemory();
Log.v("onCreate", "maxMemory:" + Long.toString(maxMemory));

Este método informa quantos bytes totais de heap seu aplicativo tem permissão para usar.

Para o item 2 acima: getMemoryClass()

que pode ser chamado da seguinte maneira:

ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
int memoryClass = am.getMemoryClass();
Log.v("onCreate", "memoryClass:" + Integer.toString(memoryClass));

Esse método informa aproximadamente quantos megabytes de heap seu aplicativo deve usar se quiser respeitar adequadamente os limites do dispositivo atual e os direitos de outros aplicativos de serem executados sem serem repetidamente forçados ao ciclo onStop()/ onResume(), pois são rudes. fica sem memória enquanto o aplicativo elefantino toma banho no jacuzzi Android.

Essa distinção não está claramente documentada, até onde eu sei, mas testei essa hipótese em cinco dispositivos Android diferentes (veja abaixo) e confirmei, para minha satisfação, que esta é uma interpretação correta.

Para uma versão padrão do Android, maxMemory()normalmente retornará aproximadamente o mesmo número de megabytes indicado em getMemoryClass()(isto é, aproximadamente um milhão de vezes o último valor).

A única situação (da qual eu sei) para a qual os dois métodos podem divergir é em um dispositivo raiz executando uma versão Android como o CyanogenMod, que permite ao usuário selecionar manualmente o tamanho de heap permitido para cada aplicativo. No CM, por exemplo, esta opção aparece em "Configurações do CyanogenMod" / "Desempenho" / "Tamanho da pilha da VM".

NOTA: Esteja ciente de que a configuração manual desse valor pode prejudicar o sistema, especialmente se você selecionar um valor menor que o normal para o seu dispositivo.

Aqui estão meus resultados de teste, mostrando os valores retornados por maxMemory()e getMemoryClass()para quatro dispositivos diferentes executando o CyanogenMod, usando dois valores de heap diferentes (definidos manualmente) para cada um:

  • G1:
    • Com o tamanho de heap da VM definido como 16 MB:
      • maxMemory: 16777216
      • getMemoryClass: 16
    • Com o tamanho de heap da VM definido como 24 MB:
      • maxMemory: 25165824
      • getMemoryClass: 16
  • Moto Droid:
    • Com o tamanho de heap da VM definido como 24 MB:
      • maxMemory: 25165824
      • getMemoryClass: 24
    • Com o tamanho de heap da VM definido como 16 MB:
      • maxMemory: 16777216
      • getMemoryClass: 24
  • Nexus One:
    • Com o tamanho do heap da VM definido como 32 MB:
      • maxMemory: 33554432
      • getMemoryClass: 32
    • Com o tamanho do heap da VM definido como 24 MB:
      • maxMemory: 25165824
      • getMemoryClass: 32
  • Viewsonic GTab:
    • Com o tamanho de heap da VM definido como 32:
      • maxMemory: 33554432
      • getMemoryClass: 32
    • Com o tamanho de heap da VM definido como 64:
      • maxMemory: 67108864
      • getMemoryClass: 32

Além do exposto, testei em um tablet Novo7 Paladin com Ice Cream Sandwich. Essa era essencialmente uma versão de estoque do ICS, exceto que eu enraizei o tablet através de um processo simples que não substitui todo o sistema operacional e, em particular, não fornece uma interface que permita que o tamanho do heap seja ajustado manualmente.

Para esse dispositivo, aqui estão os resultados:

  • Novo7
    • maxMemory: 62914560
    • getMemoryClass: 60

Também (por Kishore em um comentário abaixo):

  • HTC One X
    • maxMemory: 67108864
    • getMemoryClass: 64

E (pelo comentário de akauppi):

  • Samsung Galaxy Core Plus
    • maxMemory: (Não especificado no comentário)
    • getMemoryClass: 48
    • largeMemoryClass: 128

Por um comentário de cmcromance:

  • Grande pilha do Galaxy S3 (Jelly Bean)
    • maxMemory: 268435456
    • getMemoryClass: 64

E (comentários de tencent):

  • LG Nexus 5 (4.4.3) normal
    • maxMemory: 201326592
    • getMemoryClass: 192
  • Pilha grande de LG Nexus 5 (4.4.3)
    • maxMemory: 536870912
    • getMemoryClass: 192
  • Galaxy Nexus (4.3) normal
    • maxMemory: 100663296
    • getMemoryClass: 96
  • Pilha grande do Galaxy Nexus (4.3)
    • maxMemory: 268435456
    • getMemoryClass: 96
  • Galaxy S4 Play Store (4.4.2) normal
    • maxMemory: 201326592
    • getMemoryClass: 192
  • Grande pilha do Galaxy S4 Play Store Edition (4.4.2)
    • maxMemory: 536870912
    • getMemoryClass: 192

Outros dispositivos

  • Huawei Nexus 6P (6.0.1) normal
    • maxMemory: 201326592
    • getMemoryClass: 192

Não testei esses dois métodos usando a opção especial manifest: android: largeHeap = "true" disponível desde o Honeycomb, mas, graças ao cmcromance e ao tencent, temos alguns exemplos de valores largeHeap, conforme relatado acima.

Minha expectativa (que parece ser suportada pelos números grandes do heap acima) seria que essa opção tivesse um efeito semelhante à configuração manual do heap por meio de um sistema operacional raiz - ou seja, aumentaria o valor maxMemory()ao sair getMemoryClass()sozinho. Há outro método, getLargeMemoryClass (), que indica quanta memória é permitida para um aplicativo usando a configuração largeHeap. A documentação para getLargeMemoryClass () afirma: "a maioria dos aplicativos não precisa dessa quantidade de memória e deve permanecer com o limite de getMemoryClass ()".

Se eu adivinhei corretamente, usar essa opção teria os mesmos benefícios (e perigos) que o espaço disponibilizado por um usuário que aumentou a pilha por meio de um sistema operacional raiz (por exemplo, se o aplicativo usar memória adicional, provavelmente não funcionará tão bem com outros aplicativos que o usuário esteja executando ao mesmo tempo).

Observe que a classe de memória aparentemente não precisa ter um múltiplo de 8 MB.

Podemos ver, acima, que o getMemoryClass()resultado é imutável para uma determinada configuração de dispositivo / SO, enquanto o valor maxMemory () muda quando o heap é definido de forma diferente pelo usuário.

Minha própria experiência prática é que, no G1 (que tem uma classe de memória de 16), se eu selecionar manualmente 24 MB como o tamanho da pilha, posso executar sem erros, mesmo quando meu uso de memória puder subir para 20 MB (presumivelmente chegam a 24 MB, embora eu não tenha tentado isso). Mas outros aplicativos igualmente grandes podem ser lavados da memória como resultado da porcaria do meu próprio aplicativo. E, por outro lado, meu aplicativo pode ficar sem memória se esses outros aplicativos de alta manutenção forem trazidos ao primeiro plano pelo usuário.

Portanto, você não pode exceder a quantidade de memória especificada por maxMemory(). E, você deve tentar permanecer dentro dos limites especificados por getMemoryClass(). Uma maneira de fazer isso, se tudo mais falhar, pode ser limitar a funcionalidade desses dispositivos de maneira a economizar memória.

Por fim, se você planeja ultrapassar o número de megabytes especificado em getMemoryClass(), meu conselho é trabalhar arduamente para salvar e restaurar o estado do aplicativo, para que a experiência do usuário seja praticamente ininterrupta se ocorrer um onStop()/ onResume()ciclo.

No meu caso, por motivos de desempenho, estou limitando meu aplicativo a dispositivos com a versão 2.2 e superior, e isso significa que quase todos os dispositivos que executam meu aplicativo terão uma memoryClass de 24 ou superior. Para que eu possa projetar para ocupar até 20 MB de heap e me sentir bastante confiante de que meu aplicativo funcionará bem com os outros aplicativos que o usuário possa estar executando ao mesmo tempo.

Mas sempre haverá alguns usuários enraizados que carregaram uma versão 2.2 ou superior do Android em um dispositivo mais antigo (por exemplo, um G1). Quando você encontra essa configuração, o ideal é reduzir o uso de memória, mesmo que maxMemory()esteja lhe dizendo que você pode ir muito mais alto do que os 16 MB que getMemoryClass()estão lhe dizendo que você deveria estar alvejando. E se você não pode garantir com segurança que a sua aplicação vai viver dentro desse orçamento, em seguida, pelo menos, fazer-se de que onStop()/ onResume()funciona perfeitamente.

getMemoryClass(), como indicado por Diane Hackborn (hackbod) acima, só está disponível no nível 5 da API (Android 2.0) e, como aconselha, você pode assumir que o hardware físico de qualquer dispositivo executando uma versão anterior do sistema operacional foi projetado para oferecer suporte ideal a aplicativos que ocupam um espaço de heap não superior a 16 MB.

Por outro lado, maxMemory()de acordo com a documentação, está disponível todo o caminho de volta ao nível API 1. maxMemory(), em uma pré-versão 2.0, provavelmente irá retornar um valor de 16 MB, mas eu fazer ver que em meus (muito mais tarde) versões CyanogenMod do usuário pode selecionar um valor de heap tão baixo quanto 12MB, o que presumivelmente resultaria em um limite de heap mais baixo e, portanto, sugiro que você continue testando o maxMemory()valor, mesmo para versões do sistema operacional anteriores à 2.0. Você pode até se recusar a executar no caso improvável de que esse valor seja definido ainda mais baixo que 16 MB, se você precisar ter mais do que o maxMemory()indicado é permitido.

Carl
fonte
2
@ Carl Carl Olá, obrigado por seu ótimo post. E eu testei a opção, android: largeHeap = "true" no Galaxy S3 com JellyBean. Aqui está o resultado. [maxMemory: 256,0MB, memoryClass: 64MB] (maxMemory tinha 64 MB sem a opção largeheap)
cmcromance 4/13/13
1
@Carl Haha É uma grande honra! :)
cmcromance 5/09/13
1
Para o HTC One X: maxMemory: 67108864 memoryClass: 64
Kishore
1
LG Nexus 5 (4.4.3) (normal): maxMemory: 201326592, memoryClass: 192 | LG Nexus 5 (4.4.3) (heap grande): maxMemory: 536870912, memoryClass: 192
ian.shaun.thomas
1
Muito bem documentado. Forneça isso para o Android. Não consigo encontrar esse documento Android apropriado
Jimit Patel
20

A API oficial é:

Isso foi introduzido no 2.0, onde dispositivos de memória maiores apareceram. Você pode supor que os dispositivos executando versões anteriores do sistema operacional estejam usando a classe de memória original (16).

hackbod
fonte
13

Debug.getNativeHeapSize()vai fazer o truque, eu acho. Está presente desde a versão 1.0, no entanto.

A Debugclasse possui muitos métodos excelentes para rastrear alocações e outras preocupações com o desempenho. Além disso, se você precisar detectar uma situação de pouca memória, verifique Activity.onLowMemory().

Neil Traft
fonte
Obrigado Neil. Isso funciona quando o aplicativo está sendo executado com a depuração desativada?
Hpique
2
Sou bastante novo nisso, mas não acho que o heap nativo seja o mesmo que o heap de aplicativo (Dalvik) ao qual a pergunta se refere. O heap nativo fornece suporte para dados de bitmap, que são alocados pelo código nativo, enquanto o heap do aplicativo retém dados do aplicativo Java. Fiquei interessado em saber que o heap nativo é contado no limite do heap do aplicativo e, após o 3.0, as alocações realmente ocorrem no heap do aplicativo. Diane Hackborn (hackbod) publica este tópico aqui: stackoverflow.com/questions/1945142/bitmaps-in-android Mas mesmo para o 3.0, também existem dados não "nativos" na pilha de aplicativos.
24242 Carl
1
Desde algumas atualizações recentes do Android (talvez KitKat 4.4), os bitmaps estão no heap da JVM.
akauppi
13

Aqui está como você faz isso:

Obtendo o tamanho máximo de heap que o aplicativo pode usar:

Runtime runtime = Runtime.getRuntime();
long maxMemory=runtime.maxMemory();

Obtendo quanto da pilha seu aplicativo usa atualmente:

long usedMemory=runtime.totalMemory() - runtime.freeMemory();

Obtendo quanto da pilha seu aplicativo agora pode usar (memória disponível):

long availableMemory=maxMemory-usedMemory;

E, para formatar bem cada um deles, você pode usar:

String formattedMemorySize=Formatter.formatShortFileSize(context,memorySize); 
desenvolvedor android
fonte
5

Isso retorna o tamanho máximo da pilha em bytes:

Runtime.getRuntime().maxMemory()

Eu estava usando ActivityManager.getMemoryClass (), mas no CyanogenMod 7 (não testei em outro lugar) ele retornará um valor errado se o usuário definir o tamanho da pilha manualmente.

fhucho
fonte
Como o documento for getMemoryClassparece indicar que o número pode não ser o mesmo que o tamanho de heap disponível para sua vm, e como o documento getNativeHeapSizeé ... taciturno, eu realmente acho que Runtime.getRuntime().maxMemory()é a melhor resposta.
BoD
3

Algumas operações são mais rápidas que o gerenciador de espaço heap java. Atrasar as operações por algum tempo pode liberar espaço na memória. Você pode usar este método para escapar do erro de tamanho de heap:

waitForGarbageCollector(new Runnable() {
  @Override
  public void run() {

    // Your operations.
  }
});

/**
 * Measure used memory and give garbage collector time to free up some
 * space.
 *
 * @param callback Callback operations to be done when memory is free.
 */
public static void waitForGarbageCollector(final Runnable callback) {

  Runtime runtime;
  long maxMemory;
  long usedMemory;
  double availableMemoryPercentage = 1.0;
  final double MIN_AVAILABLE_MEMORY_PERCENTAGE = 0.1;
  final int DELAY_TIME = 5 * 1000;

  runtime =
    Runtime.getRuntime();

  maxMemory =
    runtime.maxMemory();

  usedMemory =
    runtime.totalMemory() -
    runtime.freeMemory();

  availableMemoryPercentage =
    1 -
    (double) usedMemory /
    maxMemory;

  if (availableMemoryPercentage < MIN_AVAILABLE_MEMORY_PERCENTAGE) {

    try {
      Thread.sleep(DELAY_TIME);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }

    waitForGarbageCollector(
      callback);
  } else {

    // Memory resources are availavle, go to next operation:

    callback.run();
  }
}
Zon
fonte
testado Bom. Você pode elaborar essas duas coisas: availableMemoryPercentage e MIN_AVAILABLE_MEMORY_PERCENTAGE? algumas explicações?
Noor Hossain
1
AvailableMemoryPercentageestá de acordo com a fórmula: quanto de memória do dispositivo está atualmente livre. MIN_AVAILABLE_MEMORY_PERCENTAGEé seu parâmetro customizado, um limite no qual você começa a aguardar que o coletor de lixo faça seu trabalho.
Zon
1

Asus Nexus 7 (2013) 32Gig: getMemoryClass () = 192 maxMemory () = 201326592

Cometi o erro de criar um protótipo do meu jogo no Nexus 7 e depois descobrir que ele ficou sem memória quase imediatamente no tablet genérico 4.04 da minha esposa (memoryclass 48, maxmemory 50331648)

Precisarei reestruturar meu projeto para carregar menos recursos quando determinar que a classe de memória está baixa.
Existe uma maneira em Java para ver o tamanho atual da pilha? (Eu posso ver isso claramente no logCat durante a depuração, mas eu gostaria de uma maneira de vê-lo no código para se adaptar, como se o currentheap> (maxmemory / 2) descarregasse bitmaps de alta qualidade e carregasse baixa qualidade

Tkraindesigns
fonte
No referido Nexus7-2013, getLargeMemoryClass () = 512.
akauppi
0

Você quer dizer programaticamente ou apenas enquanto está desenvolvendo e depurando? Nesse último caso, é possível ver essas informações da perspectiva DDMS no Eclipse. Quando o emulador (possivelmente o telefone físico conectado) estiver em execução, ele listará os processos ativos em uma janela à esquerda. Você pode selecioná-lo e há uma opção para rastrear as alocações de heap.

Steve Haley
fonte
Quero dizer programaticamente. Esclareceu a pergunta. Obrigado.
Hpique
1
Eu sugiro que você olhar para este post por um dos programadores da plataforma Android, em seguida: stackoverflow.com/questions/2298208/...
Steve Haley
0
Runtime rt = Runtime.getRuntime();
rt.maxMemory()

o valor é b

ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
am.getMemoryClass()

o valor é MB

qinqie
fonte
Que tipo de rt? Onde é declarado?
FindOut_Quran