This is the second in a series of articles on image handling in Android. Check out my first post on using remote images in lists if you missed it.
Put simply, image handling can take its sweet time on slower devices. Once downloaded, or loaded from file, or processed, or all of the above, it would be ideal to cache any images to be quickly loaded from memory later. Simply caching images in memory creates another challenge, as there may not be much memory free on the device, and each app has a set limit.
An ideal solution to this problem is a cache which will automatically evict images once it’s full. There are various ways to choose which items to evict when the time comes. The most efficient method would be to evict items which aren’t needed for the longest time. This is usually very difficult to predict. An item could be picked at random, which will sometimes be efficient, and sometimes not. A happy medium is an LRU cache, or Least Recently Used cache. This technique involves evicting the oldest cache entry first.
Android provides a ready-made LRU cache, called, you guessed it LruCache. The default mechanism of this cache evicts an item when its count reaches a specified number. This won’t work very well for bitmaps, which may vary hugely in size. Very conveniently, the documentation describes how to create an LruCache which is limited, not by number of items, but by memory footprint. We can do this by subclassing LruCache, and overriding the sizeOf(K, V) method. Android calls this method for each item to determine its footprint within the cache. Android checks the sum of all the sizeOf calls and compares it to the total allowed size, provided to the cache instance in its constructor. The method returns the number 1 by default. This means that by default, the cache reaches its limit when the count of items reaches the allowed size. If we override this method to return the actual size of each entry in kilobytes, and provide the cache limit in kilobytes on instantiation, the cache will become full when the byte limit is exceeded.
The method to get the size of a bitmap, getByteCount(), is missing in Xamarin’s Bitmap class wrapper, so we’ll need to invoke the method on the Java object directly. You could alternatively use getRowBytes() instead, multiplying by the bitmap height.
Whatever you do, don’t use all of an apps available memory for your cache. I recommend a maximum of about an eighth, but you can play around with this figure trying to keep it as small as possible, whilst storing enough information to curtail your image processing.
You can easily calculate the memory figure when creating your cache.
To add an image to the cache, use the put(K, V) method, and to retrieve an item from the cache, use the get(K) method. The get method will return null if the item does does exist. Either it’s not been added yet, or it’s been evicted.
So there it is. A simple memory limited LRU cache. Now your cpu-hungry or time-draining image processing can be performed as seldom as necessary, and your app won’t crash if you load too many images.