152
votes

I've got an Android app developed, and I'm at the point of a phone app development where everything seems to be working well and you want to declare victory and ship, but you know there just have to be some memory and resource leaks in there; and there's only 16mb of heap on the Android and its apparently surprisingly easy to leak in an Android app.

I've been looking around and so far have only been able to dig up info on 'hprof' and 'traceview' and neither gets a lot of favorable reviews.

What tools or methods have you come across or developed and care to share maybe in an OS project?

7
Can I vote to have Р̀СТȢѸ́ФХѾЦЧШЩЪЫЬѢѤЮѦѪѨѬѠѺѮѰѲѴ change his name to something human readable.JPM
would it be ok to reopen this question if we remove the word "Android Tools" from the question-title? The answers found here were quite usefull to solve my memory leak problemsk3b
Okay, so I have a user constantly switching between activities, meaning they may have switched 15 activities in 20 seconds. Could that be a cause of an out of memory error? What should I do to fix it? Thanks!Ruchir Baronia
Can't provide this as a response because the question has been closed, however I'd recommend taking a look at Leak Canary. Just use your app, open and close activities and let the library do it's job. It will even tell you about where the leak occured. Just give the leak analyzer some time to do its work after the leak occured - it usually takes around 2 minutes or more until the source of the leak has been found. After that it will present it to you neatly in-app. No extra tools needed!ubuntudroid

7 Answers

90
votes

One of the most common errors that I found developing Android Apps is the “java.lang.OutOfMemoryError: Bitmap Size Exceeds VM Budget” error. I found this error frecuently on activities using lots of bitmaps after changing orientation: the Activity is destroyed, created again and the layouts are “inflated” from the XML consuming the VM memory avaiable for bitmaps.

Bitmaps on the previous activity layout are not properly deallocated by the garbage collector because they have crossed references to their activity. After many experiments I found a quite good solution for this problem.

First, set the “id” attribute on the parent view of your XML layout:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="fill_parent"
     android:layout_height="fill_parent"
     android:id="@+id/RootView"
     >
     ...

Then, on the onDestroy() method of your Activity, call the unbindDrawables() method passing a refence to the parent View and then do a System.gc()

@Override
protected void onDestroy() {
    super.onDestroy();

    unbindDrawables(findViewById(R.id.RootView));
    System.gc();
}


private void unbindDrawables(View view) {

    if (view.getBackground() != null) {
        view.getBackground().setCallback(null);
    }

    if (view instanceof ViewGroup) {
        for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
            unbindDrawables(((ViewGroup) view).getChildAt(i));
        }

        ((ViewGroup) view).removeAllViews();
    }
}

This unbindDrawables() method explores the view tree recursively and:

  1. Removes callbacks on all the background drawables
  2. Removes childs on every viewgroup
28
votes

Mostly for Google travelers from the future:

Most java tools are unfortunately unsuitable for this task, because they only analyze the JVM-Heap. Every Android Application also has a native heap, though, which also has to fit within the ~16 MB limit. It's usually used for bitmap data, for example. So you can run quite easily into Out Of Memory errors even though your JVM-Heap is chillin around 3 MBs, if you use a lot of drawables.

20
votes

The answer from @hp.android works well if you are just working with bitmap backgrounds but, in my case, I had a BaseAdapter providing a set of ImageViews for a GridView. I modified the unbindDrawables() method as advised so that the condition is:

if (view instanceof ViewGroup && !(view instanceof AdapterView)) {
  ...
}

but the problem then is that the recursive method never processes the children of the AdapterView. To address this, I instead did the following:

if (view instanceof ViewGroup) {
  ViewGroup viewGroup = (ViewGroup) view;
  for (int i = 0; i < viewGroup.getChildCount(); i++)
    unbindDrawables(viewGroup.getChildAt(i));

  if (!(view instanceof AdapterView))
    viewGroup.removeAllViews();
}

so that the children of the AdapterView are still processed -- the method just doesn't attempt to remove all children (which is unsupported).

This doesn't quite fix the problem however since ImageViews manage a bitmap that is not their background. I therefore added the following. It's not ideal but it works:

if (view instanceof ImageView) {
  ImageView imageView = (ImageView) view;
  imageView.setImageBitmap(null);
}

Overall the unbindDrawables() method is then:

private void unbindDrawables(View view) {
  if (view.getBackground() != null)
    view.getBackground().setCallback(null);

  if (view instanceof ImageView) {
    ImageView imageView = (ImageView) view;
    imageView.setImageBitmap(null);
  } else if (view instanceof ViewGroup) {
    ViewGroup viewGroup = (ViewGroup) view;
    for (int i = 0; i < viewGroup.getChildCount(); i++)
    unbindDrawables(viewGroup.getChildAt(i));

    if (!(view instanceof AdapterView))
      viewGroup.removeAllViews();
  }
}

I'm hoping there is a more principled approach to freeing up such resources.

12
votes

Good Google I/O talk (2011) on Memory Management in Android, as well as details on tools + techniques for memory profiling:
http://www.youtube.com/watch?v=_CruQY55HOk

4
votes

Valgrind has been ported to Android (sponsored by Mozilla). See Valgrind on Android — Current Status and Support Running Valgrind for Android on ARM (comment 67).

1
votes

Well, those are the tools that hook with the unique formats that Android uses..I think what you may be unsatisfied with is the underlying testing code framework in use..

Have you tried mock testing areas of code using the Android Mock Framework?