I wanted to share some info that I have been learning about in my latest project. It seems that Android has an interesting way of memory management for Bitmaps. Drawables seem to act normal and get cleaned up with the garbage collector, but Bitmaps are apparently not in the normal application heap. This causes problems… for me anyway. There is an awesome article about it here if you want to learn more about it and how to find memory leaks in general.
Basically the solution to this problem is to manually recycle any Bitmaps you create once you are done using them. This seems pretty simple because you can basically call recycle() on all your bitmaps in the onDestroy() hook in your Activity. However, most of the time you have a separate thread running that is using these image resources. If you recycle your bitmaps in the Activity, there is no guarantee that your background thread won’t come searching for these resources before it dies.
My solution: I created an “ImageManager” class.
class ImageManager {
private HashMap<Integer, Bitmap> mBitmaps;
private HashMap<Integer, Drawable> mDrawables;
private Context mContext;
private boolean mActive = true;
public ImageManager(Context c) {
mBitmaps = new HashMap<Integer, Bitmap>();
mDrawables = new HashMap<Integer, Drawable>();
mContext = c;
}
// We need to share and cache resources between objects to save on memory.
public Bitmap getBitmap(int resource) {
if (mActive) {
if (!mBitmaps.containsKey(resource)) {
mBitmaps.put(resource,
BitmapFactory.decodeResource(mContext.getResources(), resource));
}
return mBitmaps.get(resource);
}
return null;
}
public Drawable getDrawable(int resource) {
if (mActive) {
if (!mDrawables.containsKey(resource)) {
mDrawables.put(resource, mContext.getResources().getDrawable(resource));
}
return mDrawables.get(resource);
}
return null;
}
public void recycleBitmaps() {
Iterator itr = mBitmaps.entrySet().iterator();
while (itr.hasNext()) {
Map.Entry e = (Map.Entry)itr.next();
((Bitmap) e.getValue()).recycle();
}
mBitmaps.clear();
}
public ImageManager setActive(boolean b) {
mActive = b;
return this;
}
public boolean isActive() {
return mActive;
}
}
This does two things: first, it makes it easy to recycle all your bitmaps and keep track of when you have recycled them to shut down your app, and second, It enables you to share repeated resources. In my app, I have images that I use many instances of. Previously I would instantiate a new Drawable or Bitmap for these using up memory for a resource I already had in memory. When you get a resource from the ImageManager, because it keys on the drawable ID, it checks to see if it is already available before loading it again.
It also makes it a little easier to get an image. the ImageManager object you create will have to be passed to every object you want to use it, but when it’s available just call im.getDrawable(R.drawable.whatever) to get your Drawable.
You will still have the problem of the background thread trying to access assets after they are recycled. To avoid this you will have to either check every time to see if the ImageManager is still active, or (the lazy way) catch the NullPointerException it throws when it tries to draw.