Image caching with volley

This post is outdated and I urge you to go have a look at the official google training material instead.

When using Volley you will run into the common problem of trying to cache images pretty soon, or you won’t if your app isn’t downloading any images. If you do rely on downloading images in you app and you want to save bandwidth, volley doesn’t do the full job for you. But almost.
A disk cache is provided, found in the static class com.android.volley.toolbox.Volley in the newRequestQueue(…) method and works out of the box. When it comes to in-memory caching there is little support from the volley lib. However in-memory caching is only used by the ImageLoader (com.android.volley.toolbox.ImageLoader), so if you are not planning on using images that are downloaded from the net, you don’t need to read further.

An example of what will suffice as an in-memory cache implementation for most apps is given below, as well as a walkthrough of the no-obvious parts of this short cache implementation.

public class BitmapMemCache extends LruCache<string, Bitmap> implements ImageCache {

    public BitmapMemCache() {
        this((int) (Runtime.getRuntime().maxMemory() / 1024) / 8);
    }

    public BitmapMemCache(int sizeInKiloBytes) {
        super(sizeInKiloBytes);
    }

    @Override
    protected int sizeOf(String key, Bitmap bitmap) {
        int size = bitmap.getByteCount() / 1024;
        return size;
    }

    public boolean contains(String key) {
        return get(key) != null;
    }

    public Bitmap getBitmap(String key) {
        Bitmap bitmap = get(key);
        return bitmap;
    }

    public void putBitmap(String url, Bitmap bitmap) {
        put(url, bitmap);
    }
}

We will extend the LruCache since that is a very convenient start implementation, due to it being generic enough to allow us to cache bitmaps with string (url) keys. More information about how the LruCache works can be found at: http://developer.android.com/reference/android/util/LruCache.html
The only thing that really needs some explaining is the magic numbers used above. And that explanation depends on a basic understanding of the was java allocated memory for a VM.
A extract from http://docs.oracle.com/javase/7/docs/api/java/lang/Runtime.html

totalMemory()
Returns the total amount of memory in the Java virtual machine. The value returned by this method may vary over time, depending on the host environment. Note that the amount of memory required to hold an object of any given type may be implementation-dependent.

maxMemory()
Returns the maximum amount of memory that the Java virtual machine will attempt to use. If there is no inherent limit then the value Long.MAX_VALUE will be returned.

freeMemory()
Returns the amount of free memory in the Java Virtual Machine. Calling the gc method may result in increasing the value returned by freeMemory.
In reference to your question, maxMemory() returns the -Xmx value.

You may be wondering why there is a totalMemory() AND a maxMemory(). The answer is that the JVM allocates memory lazily. Lets say you start your Java process as such:
java -Xms64m -Xmx1024m Foo
Your process starts with 64mb of memory, and if and when it needs more (up to 1024m), it will allocate memory. totalMemory() corresponds to the amount of memory currently available to the JVM for Foo. If the JVM needs more memory, it will lazily allocate it up to the maximum memory. If you run with-Xms1024m -Xmx1024m, the value you get from totalMemory() and maxMemory() will be equal.

So given this its clear that we want to use maxMemory() and divide that with some number (in the example above we used /8 to get a fraction of the memory that the application can allocate for itself. We also divide both the Runtime.getRuntime().maxMemory() and bitmap.getByteCount() by 1024, this is a arbitrary number used to bring down the size to numbers that will always (until memory in phones becomes very large) fit inside a int. A int would overflow after 2 Gb unless we did this division.

This Post Has 3 Comments

  1. Well said.. thanks a lot

  2. Why did you use 1/8 part of maxMemory() instead of part of freeMemory() ? I mean 1/8 of maxMemory is not granted to be free for you.

    1. Hi Bogdan,

      Look into DDMS (Device monitor) => Heap as your app runs.
      It should clear why you can’t use freeMemory – Free memory is always varying as the GC runs and as it adjusts the total memory. As Carl explains the total memory is increased lazily so technically the free memory can be 0 and still not have reached the max memory size.
      free = total – allocated
      Something like this:
      (Max heap size) MaxMemory: 64MB
      (Heap size) TotalMemory: 30MB
      Allocated: 26MB
      Free: 4MB

      Thanks Carl for the great post.

Leave a Reply

Close Menu