emmby
emmby

Reputation: 100462

Using caching to improve scrolling performance in an android ListView with images

I'm currently using a filesystem cache to cache my images as I download them from the server. I have a ListView that contains custom views, each of which retrieves its image from the filesystem when getView() is called.

To improve performance, I implemented a java.util.WeakHashMap<String,Bitmap> that stores each of the bitmaps by a unique key. This allows me to tuck the images into the hashmap as they're downloaded, and then retrieve them directly from memory to populate my listview. This avoids a file I/O operation and results in a much smoother scrolling experience.

The idea is that as the OS runs low on memory, it will clean out the WeakHashMap to free up memory.

However, this doesn't work on Android 2.3 or earlier. The problem is that bitmaps are not kept in the Java Heap, and are instead kept in native memory. This means that the JVM garbage collector has no idea how much memory those images are occupying, and thus never bothers to free them up when the OS is low on native memory, resulting in OutOfMemory errors when there's plenty of memory that can still be reclaimed.

This has been fixed in Android 3.0, since 3.0 stores bitmaps in JVM heap instead of native memory, but the question is how can I easily cache bitmaps on Android 2.3 and later without inadvertently causing unnecessary OutOfMemory exceptions?

Upvotes: 1

Views: 3104

Answers (3)

emmby
emmby

Reputation: 100462

So the answer seems to be that there's a bug in the way the dalvik VM detects when it needs to do a GC pass. If you manually call System.gc() immediately before allocating memory for your bitmap, the OutOfMemory errors surprisingly go away.

    if(Build.VERSION.SDK_INT < 12) {
        Log.d("Running garbage collection for Bitmaps");
        System.gc();
    }
    return BitmapFactory.decodeStream(is);

Obviously, the VM should be doing this GC automatically before it throws an OutOfMemory, but it does not appear to do so.

Upvotes: 1

cyngus
cyngus

Reputation: 1763

So, even though Bitmap memory is allocated on the native heap in earlier versions of Android, this memory is still charged against your process, its just harder to see, this is why you might get an OOM Exception. However, your basic analysis is correct though. The problem is that the native code doesn't really have a good idea when it can deallocate memory for Bitmaps, which is why its recommended that developers all Bitmap.recycle(), since this essentially tells native code that its okay to free the memory. Likely when items are removed from the WeakHashMap, this isn't being called.

However, empirically I'd built a similar system using a HashMap<String, SoftReference<Bitmap>> and Bitmap memory was properly freed. I'll note though that I think this solution became less effective starting in Android 2.3 because of changes to the garbage collector, although I'd need to go back and verify this remembrance.

In the end I guess the answer is that I don't know of a good answer to this question that doesn't use explicit management like the LruCache. It would be great to have a solution that uses SoftReferences or WeakReferences, but with the current way we do garbage collection I'm not sure this will work.

Upvotes: 0

adamp
adamp

Reputation: 28932

You might try something like this: http://code.google.com/p/xlarge-demos/source/browse/trunk/PhotoAlbum/src/com/example/android/photoalbum/LruCache.java and explicitly recycle() your Bitmaps in the evicted step.

Upvotes: 2

Related Questions