OutOfMemory error – do you use runnables?

This is how I fixed my OutOfMemoryError, I don’t claim to be 100% correct and forgive me when I try to explain how, what and why (because I’ll probably just do it wrong) but hopefully me giving my description of this issue will lead you to fixing your own defects.

Argh my latest application was running into out of memory and giving a Force Close over and over again it was so annoying. I think I’m a half decent programmer and I was checking the obvious *I thought*. Sitting down with the problem I took code out bit by bit to isolate the problem. I found the cause! Now to find the answer.

So let’s wind it back a bit, I was creating an Android Gallery that automatically scrolled itself, the way I do this is to use a runnable with a delay that will fire the movement effect after a set amount of time. I was using bitmaps in my gallery so this lead me to think the issue could lie there, it was a red herring in the end.

Here is the error in a nut shell:

02-16 12:04:20.144: D/dalvikvm(2050): GC_EXTERNAL_ALLOC freed 87K, 47% free 3060K/5703K, external 9694K/10218K, paused 20ms
** Rotate Screen **
02-16 12:04:20.144: D/dalvikvm(2050): GC_EXTERNAL_ALLOC freed 87K, 47% free 3060K/5703K, external 15694K/16645K, paused 20ms
** Rotate Screen **
02-16 12:04:20.144: D/dalvikvm(2050): GC_EXTERNAL_ALLOC freed 87K, 47% free 3060K/5703K, external 21694K/21748K, paused 20ms
** Rotate Screen **
BOOM java.lang.OutOfMemoryException

Garbage Collection Codes explained:

GC_FOR_MALLOC means that the GC was triggered because there wasn’t enough memory left on the heap to perform an allocation. Might be triggered when new objects are being created.

GC_EXPLICIT means that the garbage collector has been explicitly asked to collect, instead of being triggered by high water marks in the heap. Happens all over the place, but most likely when a thread is being killed or when a binder communication is taken down. Also when you use System.gc();

GC_CONCURRENT Triggered when the heap has reached a certain amount of objects to collect. This is the default garbage collection in 2.2 and above.

GC_EXTERNAL_ALLOC means that the the VM is trying to reduce the amount of memory used for collectable objects, to make room for more non-collectable. External Alloc is a telltale sign you may have too many bitmaps.

Whats happening in the above log is, each time I rotate the screen my activity is being destroyed and recreated. The old activity should be garbage collected and the memory freed up to use elsewhere, but when this doesn’t happen it can lead to an out of memory error.

An activity may not be freed because an object it is attached to is still *live*. When an object is live anything that it is touching is ignored by the garbage collector. This can happen with runnables!

Here is a small *non compile-able* example:

package com.blundell.tut.ui.widget;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.Gallery;

import com.blundell.tut.ui.adapter.ImageAdapter;

public class BuggedAutoScrollingGallery extends Gallery {
	private static final int WAIT_BETWEEN_IMAGES = 3000;
	
	public BuggedAutoScrollingGallery(Context context) {
		super(context);
	}

	public BuggedAutoScrollingGallery(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public BuggedAutoScrollingGallery(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}
	
	/**
	 * Start the gallery auto scrolling
	 */
	public void start() {
		setAdapter(new ImageAdapter(getContext()));
		post(mAutoScrollTask);
	}
	
	private Runnable mAutoScrollTask = new Runnable(){
		public void run() {
			
			Log.d("Gallery", "Do some autoscrolling here");
			onKeyDown(KeyEvent.KEYCODE_DPAD_RIGHT, new KeyEvent(0,0));
			
			removeCallbacks(mAutoScrollTask);
			postDelayed(mAutoScrollTask, WAIT_BETWEEN_IMAGES);
		};
	};
	
	/**
	 * Stops the gallery auto scrolling
	 */
	public void tearDown(){
		removeCallbacks(mAutoScrollTask);
	}
}

The code above creates a new adapter for your gallery, by the way this is ripped out my Infinite Scrolling Gallery. It then uses a runnable to post back to the gallery allowing it move ‘on its own’. If you look at the tearDown() method we stop this runnable when we have finished with the activity.

This is the activity that would run the gallery, note the use of the activity lifecycle to manage your custom view:

package com.blundell.tut.ui.phone;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;

import com.blundell.tut.R;
import com.blundell.tut.ui.widget.BuggedAutoScrollingGallery;

public class OutOfMemoryTutActivity extends Activity {
    private BuggedAutoScrollingGallery gallery;

	/** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        gallery = (BuggedAutoScrollingGallery) findViewById(R.id.gallery);
        gallery.start();
    }
    
    public void onGotoFixedActivity(View v){
    	Intent intent = new Intent(this, FixedOutOfMemoryTutActivity.class);
    	startActivity(intent);
    }

    @Override
    protected void onStop() {
   		gallery.tearDown();
    	super.onStop();
    }
} 

When the activity is rotated the activity is destroyed, onStop() is called which calls tearDown() of our gallery. However the runnable is still alive, this means the attached gallery is alive and so this means the Activity is still alive. Now the example above is pretty simple and won’t always cause the OOM (not a big memory footprint, no complex objects – like your real system) but it shows the point.

The way to get round this is to make sure you release your runnables when you have finished with them:

/**
	 * Stops the gallery auto scrolling
	 */
	public void tearDown(){
		removeCallbacks(mAutoScrollTask);
		mAutoScrollTask = null;
	}

Hell it’s a lot more complex than that, there are many articles that explain this much better than me, but coming from it from the learners side I think this gives some valuable insight into the basics and how you can improve your code.

If you want further reading, android.developer is always a good place. Also a really really insighful video is here from Google IO 2011.

Memory leaks can be found using the eclipse plugin mat, it can also be downloaded as a standalone program.

Thanks for listening.

One thought on “OutOfMemory error – do you use runnables?

Comments are closed.