Android memory management follows a long the lines of lots of other managed memory environments. In a managed memory environment memory is automatically allocated for each new object you create and kept in memory as long as there is a reference to it from a “root”. If there is no longer a reference to it it will be marked as eligible for recollection by the garbage collector. Take a look at the graph below:
In the diagram we have a “root” object. This is usually a static member of class and is always reachable. Everything that is reachable from the root object is considered “live memory” and is not eligible for garbage collection. “Reachable” is determined by following references in each object. These are represented by solid lines in the above graph. The garbage collector would traverse this tree and find its way to objects 1, 2, and 3 but not object 4. Let’s say that #1 created object #4 but then reassigned its reference, null’ed it out or it went out of scope. The garbage collector would not be able to reach it and would mark it as eligible for garbage collection.
If the program continues to run and starts getting near its memory limit then garbage collector starts up and looks for things to reclaim. The problems arises when we hold onto references to objects that we no longer need. This is called a memory leak. If these memory leaks persist then the amount of memory used by your application will grow the more it’s used. This can result in an OutOfMemory error and is usually fatal to your application.
Outside your application, the Android OS is also monitoring your app. In a mobile device, memory resources are at a premium and most likely will need to be prioritized. Since Android does not usually use a Swap space, the least prioritized app or any that are behaving badly will get booted. This is incentive enough to to keep your memory footprint as low as possible. This can be achieved by NOT doing certain things.
Keep global/static references to a minimum or not at all
Android has a notion of an “Application” object: android.app.Application
and it is often abused. Some developers like to stuff things like User objects, caches, and state in Application objects as fields. These will live forever. If an object isn’t immediately needed it should be allowed to pass out of scope by not keeping it as a field.
A common pitfall is to keep references to a Context object. This can happen easily without explicitly doing it. The Context object is usually used to load and access resources among many other uses. It will have references to the entire view hierarchy of that activity which could have a rather large memory footprint. Here’s a simple memory leak in action:
public class ExampleActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Handler handler = new Handler();
handler.postAtTime(new Runnable() {
@Override
public void run() {
Log.d("ExampleActivity", "ran");
}
}, 10 * 1000);
}
}
What happens here is the anonymous inner class we create with the Runnable holds a reference to the class it was created in, in this case ExampleActivity
. When it’s used in postAtTime
the main Looper (which stays in memory as long as the app hasn’t been killed) holds a reference to it. If this Activity gets finished through a back button press the Activity will still remain in memory for at least as long as the Runnable is still in the queue ( 10 seconds in this case ). You can avoid this easily one of two ways. You can can make the Runnable a static implementation or cleanup the handler when the Activity finishes.
Here’s a solution for a static implementation:
public class ExampleActivity extends Activity {
public static final Runnable myRunnable = new Runnable() {
@Override
public void run() {
Log.d("ExampleActivity", "ran");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
handler.postAtTime(myRunnable, 10 * 1000);
}
}
The runnable is now a static variable and will not contain a reference to the Activity so the Activity will be a candidate for garbage collection. However, the runnable is not very useful. Let’s say we want to reference something in the Activity but we don’t care about running the Runnable if the Activity has been finished. We can use an anonymous inner but we just need to be careful to clean it up when the Activity finishes.
public class ExampleActivity extends Activity {
private Runnable runnable;
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handler = new Handler();
runnable = new Runnable() {
@Override
public void run() {
Log.d("ExampleActivity", getActionBar().getTitle().toString());
}
};
handler.postAtTime(runnable, 10 * 1000);
}
@Override
protected void onDestroy() {
handler.removeCallbacks(runnable);
super.onDestroy();
}
}
In onDestroy
we remove the runnable from the queue and the Activity will be a candidate for garbage collection.
Don’t allocate memory in a tight loop
There are few places you’ll run into in Android development where your memory allocation needs to be closely monitored. One of those places is the View.onDraw
method. Creating custom views can open a lot of opportunities for unique and interesting apps. However, you have to be wary of how you implement them.
Most the overridden methods of the View are called only when the View is first created or displayed. onDraw
however can be called many times, when the view is first rendered, when something obstructs the view, when the view is moved like in a ListView, or when a the screen is resized like in on orientation change.
Here’s a naive implementation:
public class ExampleView extends View {
public ExampleView(Context context) {
this(context, null);
}
public ExampleView(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}
public ExampleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onDraw(Canvas canvas) {
if(canvas == null || canvas.getWidth() <= 0 || canvas.getHeight() <= 0) {
return;
}
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setStrokeWidth(10.0f);
Rect rect = new Rect(0, 0, canvas.getWidth(), canvas.getHeight());
canvas.drawRect(rect, paint);
}
}
The problem here is the creation of new objects in the onDraw
method. The Android OS may need to call onDraw
many times and will create two new objects each time. The objects would then get added into memory and removed rather quickly causing the garbage collector to do a lot of work. Let’s move them into the constructor and update them when we need to.
public class ExampleView extends View {
private Paint paint;
private Rect rect;
public ExampleView(Context context) {
this(context, null);
}
public ExampleView(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}
public ExampleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
paint = new Paint();
paint.setColor(Color.RED);
paint.setStrokeWidth(10.0f);
rect = new Rect();
}
@Override
protected void onDraw(Canvas canvas) {
if(canvas == null || canvas.getWidth() <= 0 || canvas.getHeight() <= 0) {
return;
}
rect.top = 0;
rect.bottom = canvas.getHeight();
rect.left = 0;
rect.right = canvas.getWidth();
canvas.drawRect(rect, paint);
}
}
Here we see the object allocation done in the constructor instead of in the onDraw
method, the garbage collector will be much happier.
Handle bitmaps with care
Bitmaps are Android’s in-memory representations of images. As such they can potentially be quite large. For example, most phones would have trouble holding full a bitmap of an image taken with it’s own camera in memory. So if your app displays an image taken by its own camera you need to downsample it then recycle it when you are done with it. Android developer training has a great article on how to load a downsampled image into a bitmap: http://developer.android.com/training/displaying-bitmaps/load-bitmap.html.
If you want to deal with a lot of images this could become cumbersome as they will stay in memory for quite and if you are loading several in rapid succession ( like in a gallery app ) you will quickly run out of memory. Bitmap.recycle()
helps you manage this problem. When you are done with a bitmap simply call recycle()
on it and the OS knows to clear that memory immediately instead of waiting for the garbage collector to do it. This is most appropriate when an activity leaves view in onStop
or a Fragment is no longer visible. Be sure to reload that Bitmap when or if that Fragment/Activity comes back into view.
Conclusion
- Careful of global variable and errant Context references. Use static classes for callbacks when possible or make sure to do your own cleanup when they’re not needed.
- The
View.onDraw
method could potentially be called many times, make sure to not create objects in it and unload as much work to the constructor or setup methods. - The memory footprints of Bitmaps can dwarf the rest of the application. Their lifecycle is often better handled manually for optimal performance using downsampling and recyclying.