Advertisements

How to use LruCache in your Android App

In this article we are going to see how to use LruCache for storing Bitmaps. Android’s LruCache is great for temporarily storing downloaded data and therefore avoid wasting time, memory, and also battery life. This post builds on top of the previous one, in which we saw how to download images and show them inside RecyclerView using HandlerThread and Messages. It is not really necessary to read that post to understand this one. To summarize, by the end of it we had a working implementation of a RecyclerView showing images downloaded asynchronously. It is the perfect starting point for implementing a cache based optimization.

LruCache is, as the name suggests, just a cache. Lru stands for Least Recently Used, and it’s the name of the policy used to decide when an element can be removed and when it cannot. To use this class we have to declare how many entries or bytes can be cached at maximum, when the limit is reached and we try to insert new data, old data is removed to make space for it. The data which gets removed is always the least recently used.

Android LruCache example tutorial
See the complete code on GitHub

 
We are going to extend the demo app from the previous post to use LruCache. If you read the post you might remember that we had two Handler objects, one downloading images and the other one showing them inside an ImageView. The image downloaded by the first Handler was converted to a ByteArray and sent to the second Handler via the message Bundle.
That is a pretty inefficient approach because it requires converting the Bitmap twice, to and from ByteArray. At the same time the Bitmap is downloaded every time we have to show it.

LruCache to the rescue

LruCache was introduced in Android 3.1 and it is provided by the support library, therefore in order to use it we have to add this dependency to build.gradle.

implementation 'com.android.support:appcompat-v7:28.0.0'

Using this class is quite easy, we have to initialize it with a maximum size and then we can start using it as a dictionary. Any value inside the cache is addressed by a specific key. Adding a value with a key that is already in the cache will replace that value with the new one.
To create a LruCache


val lruCache = LruCache<String, Bitmap>(10)

This will create a cache that can store at maximum 10 entries. However when using bitmaps it is very hard to tell in advance how much space 10 of them require. The total amount depends on the bitmap size, might be 10 KB or 100 MB, we do not know.
It would be better to express the size of the cache in bytes, there is a trick for this. We have to create a custom LruCache subclass and override the sizeOf method. This method determines the size of each entry in the cache. The default implementation returns one for all entries.


protected int sizeOf(@NonNull K key, @NonNull V value) {
    return 1;
}

Overriding this and returning the amount of bytes occupied by each bitmap creates a cache in which the dimension provided in the constructor is expressed in bytes.
This is what I did in the demo app, my cache is 16 MB big.


class BitmapCache : LruCache<String, Bitmap>(16*1024*1024) {
    override fun sizeOf(key: String, value: Bitmap): Int {
        return value.byteCount
    }
}

Caching and retrieving Bitmaps

LruCache is very much like an HashMap, at a basic level there are two calls we need to remember.
Cache an item with


lruCache.put(key, value)

When storing an item using a key that is already present in the cache, the new item will replace the item previously associated with that key.
Retrieve an item with


val value = lruCache.get(key)

When retrieving an item that is not present in the cache, we will get null as a return value.
This is all there is to LruCache on a basic level.

Clearing the cache

We can forcefully clear the contents of the cache, I do it in the demo app when RecyclerView detaches the adapter. To do this we have to call the evictAll() method.


override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
    ...
    cache.evictAll()
}

Thread safety

LruCache is thread safe, therefore it can be used safely to store and retrieve items from different threads. Just call put() and get() and it will work. In the demo app I share the cache between the two Handler objects, the Handler associated with the HandlerThread downloads the image and uses put() to cache it. The Handler associated with the UI thread uses get() to retrieve the image. In the previous demo app the whole Bitmap was exchanged between the two handlers, in the new version only the url is shared. The url is the key associated with each Bitmap in the cache.

Conclusion

If you want to see this class in action you can clone the demo app. If you’ve read the previous post you are familiar with the HandlerThread based approach explained there. The difference between this demo app and the one linked in that article is just the use of LruCache, which makes this code actually useful in real life.

Unique opportunity! Help a fellow grow his blog!

Hi there! If you’ve read this far maybe you think this was useful, or fun, or I don’t know what but for some reason You Got Here! Great! Please consider sharing this post with your network, I am trying to get The Code Butchery to grow so I can provide more content like this, will you help me in my journey? Thank you!

Advertisements
Share this

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.