Dev (Back & Front)ARTIGO

Análise de memória para aplicativos Android

O tempo de execução do Dalvik pode
ser garbage-collected, mas isso não significa que você pode ignorar o
gerenciamento de memória. Você deve ser especialmente cuidadoso com o uso de
memória em aparelhos móveis, nos quais ela é mais reduzida. Neste artigo,
vamos dar uma olhada em alguns perfis de ferramentas do Android SDK que podem
te ajudar a equilibrar o uso de memória de seus aplicativos. 

Alguns problemas de uso de
memória são óbvios. Por exemplo, se falta memória ao seu app toda vez que o
usuário toca a tela, ele provavelmente vai, eventualmente, engatilhar um
OutOfMemoryError, e encerrar seu app. Outros problemas são mais sutis, e podem
degradar tanto a performance do seu app (uma vez que garbage collections são
mais frequentes e demoram mais tempo) e do seu sistema como um todo.

Ferramentas de troca

O Android SDK oferece duas formas
principais de traçar
o perfil do uso de memória de
um app: a aba Allocation Tracker no DDMS, e o heap dumps. O Allocation Tracker é útil quando você quer compreender que tipo de alocação está acontecendo em um
determinado período, mas ele não te dá nenhuma informação sobre o estado geral
do heap do aplicativo. Para mais informações sobre o Allocation Tracker,
veja o artigo Tracking Memory
Allocations

(inglês). A partir de agora, o foco deste artigo
serão os heap dumps, que são uma
ferramenta mais poderosa de análise de memória.

Um heap dump é um snapshot
(imagem instantânea) do heap de um aplicativo, que é armazenado em um formato
binário chamado HPROF. O Dalvik usa um formato similar, mas não idêntico, para a ferramenta HPROF
em Java
. Existem
algumas maneiras de gerar o heap dump de
um app para Android em execução. Uma delas é usar o botão Dump HPROF no DDMS. Se você necessita ser mais preciso acerca de quando o dump é criado, você
também pode criar um heap dump
programaticamente, usando a função android.os.Debug.dumpHprofData().

Para analisar um heap dump, você pode usar uma ferramenta
padrão como jhat ou Eclipse Memory Analyzer (MAT). No entanto,
primeiramente você precisa converter o arquivo hprof no formato Dalvik para o formato J2SE HPROF.
Você pode fazer isso usando a ferramenta hprof-conv, fornecida no Android
SDK. Por exemplo:

hprof-conv dump.hprof converted-dump.hprof

Exemplo: depurando um vazamento
de memória

Durante a execução do Dalvik
runtime, o programador não aloca nem liberta a memória explicitamente, então
não tem como você “vazar” memória como em linguagens como C and C++. Um
“vazamento de memória” no seu código acontece quando você mantém a referência a
um objeto que não é mais necessário. Às vezes, uma única referência pode
prevenir uma grande gama de objetos de se tornaram garbage collected.

Vamos seguir um exemplo usando
o Honeycomb
Gallery sample app
 do
Android SDK. É um simples aplicativo de galeria de fotos que demonstra como
usar alguns dos novos APIs do Honeycomb (para fazer o download e construir o
código de exemplo, veja as instruções.) Vamos adicionar,
deliberadamente, um vazamento de memória neste app com o intuito de demonstrar
como ele poderia ser debugado.

 

 Imagine que queremos modificar
esse app para carregar imagens da rede. A fim de torná-lo mais ágil,
implementaremos um cache que contém imagens recém-visualizadas. Podemos fazer
isso ao promover pequenas mudanças no ContentFragment.java. No topo da classe,
adicionaremos uma nova variável estática:

private static HashMap<String,Bitmap> sBitmapCache = new HashMap<String,Bitmap>();

Aqui é onde colocaremos o cache
dos Bitmaps que carregamos. Agora podemos mudar o método updateContentAndRecycleBitmap() para
checar o cache antes do carregamento, e para adicionar os Bitmaps ao cache depois
que eles forem carregados:

 void updateContentAndRecycleBitmap(int category, int position) {
if (mCurrentActionMode != null) {
mCurrentActionMode.finish();
}

// Get the bitmap that needs to be drawn and update the ImageView.

// Check if the Bitmap is already in the cache
String bitmapId = "" + category + "." + position;
mBitmap = sBitmapCache.get(bitmapId);

if (mBitmap == null) {
// It's not in the cache, so load the Bitmap and add it to the cache.
// DANGER! We add items to this cache without ever removing any.
mBitmap = Directory.getCategory(category).getEntry(position)
.getBitmap(getResources());
sBitmapCache.put(bitmapId, mBitmap);
}
((ImageView) getView().findViewById(R.id.image)).setImageBitmap(mBitmap);
}

Eu introduzi, deliberadamente, um
vazamento de memória aqui: adicionamos Bitmaps ao cache sem sequer os remover. Em
um app real, nós devemos limitar o tamanho do cache de alguma maneira.

Examinando o uso do heap em DDMS

O Debug Monitor Server (DDMS) do
Dalvik é uma das primeiras ferramentas para debbuging do Android. O DDMS é
parte do plug-inADT Eclipse, e uma versão autônoma também pode ser
encontrada no diretório tools/ do Android SDK. Para mais informações sobre
o DDMS, veja Usando DDMS (inglês).

Vamos usar o DDMS para examinar o
uso do heap deste app. Você pode executar o DDMS de duas maneiras:

  • pelo
    Eclipse: clique Window > Open Perspective > Other… > DDMS
  • ou pela
    linha de comando: execute ddms (ou ./ddms em Mac/Linux) no
    diretório tools/

Selecione o processo com.example.android.hcgallery no
painel da esquerda, e então clique no botão Show heap updates na
barra de ferramentas. Em seguida, inverta para a guia VM Heap no
DDMS. Ela mostra alguns dados estatísticos básicos sobre a utilização de
memória do nosso heap, atualizado após cada GC. Para ver o primeiro update, clique
no Cause GCbutton.

 

Podemos
ver que nosso grupo ativo (a coluna Allocated) está um pouco acima de
8 MB. Agora passe pelas fotos e veja o número aumentar. Como só existem 13 fotos
nesse app, a quantidade de memória que vazamos é delimitada. Em certos aspectos,
este é o pior tipo de vazamento que se pode ter, porque nós nunca temos um
OutOfMemoryError, indicando que estamos vazando.

Criando um heap dump

Vamos usar um heap dump para
rastrear o problema. Clique no botão Dump HPROF file na barra
de ferramentas do DDMS, escolha onde você quer salvar o arquivo e então execute
um hprof-conv nele. Neste exemplo, usarei a versão autônoma do MAT
(version 1.0.1), disponível no MAT download site.

Se você está executando o ADT (o
que inclui uma versão plug-in do DDMS) e tem o MAT instalado no Eclipse também,
ao clicar no botão “dump HPROF”, ele irá fazer a conversão (usando hprof-conv)
automaticamente, e abrirá o arquivo hprof convertido no Eclispe (que será
aberto pelo MAT).

Analisando heap dumps usando MAT

Execute o MAT e carregue o
arquivo convertido no HPROF que acabamos de criar. O MAT é uma ferramenta poderosa,
e está além do objetivo deste artigo explicar todos seus recursos. Portanto, eu
somente irei mostrar uma maneira de usá-lo para detectar um vazamento: através
da visualização do Histograma você pode ver a lista de classes distribuídas
pelo número de instâncias, pelo shallow heap (quantidade total de
memória usada opor todas as instâncias) ou pelo retained heap (quantidade
total de memória mantida viva por todas as instâncias, incluindo outros objetos
a quem eles se referenciam).

Se filtrarmos pelo shallow heap,
podemos ver que as instâncias byte[] estão no topo. Da mesma maneira
como no Android 3.0 (Honeycomb), os dados de pixel para objetos Bitmap são
armazenados em byte arrays (previamente eles não eram armazenados no heap do Dalvik
heap), e, baseado no tamanho desses objetos, é uma aposta segura dizer que eles
são a memória de apoio para nossos bitmaps vazados.

Clique com o botao direito na
classe byte[] e selecione List Objects > with incoming
references. Essa ação produz uma lista de todas as byte arrays dentro do
heap, que podem ser distribuídas baseadas no uso do shallow heap.

Escolha um dos objetos grandes e
veja seu detalhamento. Isso irá te mostrar o caminho a partir da
raiz definida até o objeto
– a cadeia de
referências que mantém esse objeto vivo.
E eis que aí está
nosso cache dos bitmaps!

O MAT não pode nos dizer com
certeza que esse é o vazamento, porque ele não sabe se esses objetos são
necessários ou não – só o programador sabe isso. Nesse caso, o cache está
usando uma grande quantidade de memória, relativa ao resto do aplicativo, então
devemos considerar limitar o tamanho do cache.

Comparando heap dumps com MAT

Ao debugar vazamentos de memória,
às vezes é útil comprar o estado do heap em dois momentos diferentes. Para
fazer isso, você precisará criar dois arquivos HPROF separados (não se esqueça
de convertê-los usando hprof-conv).

Veja como você pode comprar dois
heap dumps no MAT (é um pouquinho complicado):

  1. Abra o primeiro arquivo HPROF
    file (usando File > Open Heap Dump).
  2. Abra a visualização do Histograma.
  3. Na visualização do histórico de
    navegação (vá em Window > Navigation History se não estiver
    visível), clique com o botão direito em histograma e selecione Add
    to Compare Basket.
  4. Abra o segundo arquivo HPROF e
    repita os passos 2 e 3.
  5. Inverta para a visualização do  Basket view, e clique em Compare the
    Results (o ícone “!” vermelho no topo direito).

Conclusão

Neste artigo, mostrei como o Allocation
Tracker e os heap dumps podem te garantir um melhor entendimento do uso de
memória de seus aplicativos. O Eclipse Memory Analyzer (MAT) pode te ajudar a
rastrear os vazamentos de memória no seu aplicativo. O MAT é uma ferramenta
poderosa, e eu apenas toquei na superfície do que você pode fazer com ela. Se
você quiser aprender mais, recomendo a leitura de alguns destes artigos:

 

 ?

Texto original disponível
em http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html

é engenheiro de software do Android no Google. Recentemente, ele trabalhou na elaboração da home screen do Honeycomb. Antes de entrar para o Google, trabalhou na BumpTop, em Toronto. Ele escreve sobre programação, usabilidade e interação homem-computador em http://dubroy.com.

Ver perfil