Android Image Downloading and Caching

One of the utility classes we created for the Singly Android SDK is a remote image downloader and cache. We wanted to be able to download images from remote sites, store them locally, and cache them in memory. We also wanted to be able to limit the number of concurrent images we were downloading, avoid duplicate downloads, avoid retrying bad images, and more importantly optimize the images for memory consumption. In this post we will detail our RemoteImageCache class that can be dropped into any Android application to enable easy remote image downloading and caching.

Supporting Classes

To start we have some supporting classes, ImageInfo, ImageCacheListener, and BitmapUtils. The ImageInfo class is a holder class for images that we are downloading and caching. It holds fields such as the url of the image to download and the size we want to display the image. It also has an ImageCacheListener field.

The ImageCacheListener is used to do callbacks when an image has completed the download process.

The BitmapUtils holds utility methods for working with Bitmaps. The decodeAndScaleImage method is used to correctly optimize a downloaded image for the size at which it will be displayed. We determine the best sample rate for the Bitmap for the given width and height. Then decode the Bitmap using that sample rate. The Bitmap isn’t returned at the width and height, it is returned using a sampling rate that is appropriate for the desired width and height as Android will automatically scales Bitmaps in many components.

The RemoteImageCache

The meat of the downloader is the RemoteImageCache class. Here is the shell.

We have a lot going on here so let’s explain. We have multiple threads operating together. We start with a queue onto which ImageInfo objects are placed to be downloaded. That is the queue variable. Then we have a single worker thread that pulls Objects off the queue and hands them to async http clients to do the image downloading. We use a semaphore to limit the number of concurrent downloads and synchronized sets to hold the state of images being downloaded and that have errored.

Once images are downloaded, they are processed and stored in the local filesystem and then loaded into memory. We use an LruCache to limit the number of images stored in memory to optimize memory consumption. Once an image is downloaded it will not be re-downloaded. Instead it will be loaded from the local filesystem.

In our constructor we setup the local filesystem cache directory. We setup the Semaphore to throttle downloads and we startup the DownloaderThread which is our single worker thread.

To get an image a call is made to the getImage method passing in an ImageInfo. We first check if it is a bad image and if so return null. If not we try to load the image from memory. If it doesn’t exist in memory we see if it exists on disk. If it doesn’t exist on disk then we start the image download process by placing the ImageInfo on the queue. We also add the image id to the set of downloading images to prevent duplicate downloads. If the image is already downloading null is returned.

The image will go through the download process and when it is finished the ImageCacheListener methods will be called.

The shutdown method is called to shutdown the instance, usually when the activity using the RemoteImageCache is being destroyed. We first cleach the memory cache. Then we use a method known as a poison pill to stop the DownloadedThread we started earlier. This involves placing a know stop object onto the queue. The DownloaderThread picks up the stop object and exits itself.

DownloaderThread

The DownloaderThread is responsible for the downloading of remote images. It blocks on the queue waiting for an ImageInfo object to come in. If the ImageInfo is the poison pill the thread exits, otherwise it attempts to acquire the semaphore. If it is successful it can start downloading, otherwise there are already max concurrent images downloading and the thread must wait, blocking on the semaphore.

The image file is downloaed using the async http class. On both error and success the semaphore is immediately released to allow images in wait to proceed. On success the image is scaled if necessary and then stored to disk and loaded into memory. The synchronized sets are updated. On an error the image is placed into the bad set so it won’t be redownloaded again this session.

The Handler and the UI Thread

The last piece is the handler. UI updates happen in the main UI thread of an android application. In our RemoteImageCache we have threads handing off to threads. We want to tell our application that our image is done downloading but we also want it to run the callback listener in the main UI thread. The way to do this is through a handler.

The UI thread works off of a queue of events. Events that update the UI, such as clicks or redraws, are pulled off the queue and handled one at a time by the UI thread. This is why if you have a long running operation inside the main UI thread it can freeze your application. This is also why Android doesn’t allow network operations or other long running operations on the UI thread. We have to do our image downloading in a separate thread but then do any UI updates back in the main thread.

The handler.post method allows us to place a Runnable object onto the UI thread queue. In our case we run the ImageCacheListener that the application has specified. Usually this will update the UI with the image that was downloaded. More importantly these updates happen in the main UI thread.

Using the RemoteImageCache

Here is an example of how the RemoteImageCache could be used in the getView method of a ListAdapter. In this example we have rows in a list each with their own image. The images would be downloaded in the background and as each image is downloaded only that specific image is updated in the UI.

When the image is downloaded the listener is run. We use an optimization trick where if the image is inside the set of visible rows on the list view, then we get the single image view and update its bitmap with the downloaded image. This will only update the single row as opposed to doing something like notifyDataSetChanged which updates multiple rows. If you would like to see a further example, check out the Friends List in the Singly Android SDK examples.

Here is the RemoteImageCache code listing. This and other components and utilities can be found in the Singly Android SDK. Check it out if you want drop in support for authentication to multiple networks and easy access to social data. Connect with me if you are interested in or have questions about the Singly Android SDK.

4 Responses to “Android Image Downloading and Caching”

  1. Prakash Nadar says:

    Why do you have to scale down the image after downloading it?

    • Dennis Kubes says:

      The scaling reduces the in-memory size of the image according to the image’s display size. Android will scale the image to the canvas size. But because of that the image could be significantly larger than it needs to be for its on-screen display size. The scaling lets a bigger image be sampled down to a smaller size because it will be displayed in a smaller screen region. This conserves memory and allows the image to load faster.

  2. vish says:

    Thanks Dessis for nice tutorial. I am facing problem with getView() example code as you are modifying rowView inside inner call how one should return the reference back as getView needs view as return type.