Xamarin Android – Image Caching

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.

6 thoughts on “Xamarin Android – Image Caching

Add yours

  1. +1 for your nice article, I followed your previous article and implemented async download of image in side a Recyleview with Cards. Thank you for your help on this.

    I has read above article and trying to apply same in my application but I stuck where should I put below block of code
    if (_memoryCache.Get(key) == null)
    _memoryCache.Put(key, bitmap);
    and also where can I create a _memoryCache object.

    I’m trying to implement a ListView using Recyleview and Cards.

    your previous article code I written in Adapter OnBindViewHolder method.
    For your reference I’m placing code which I put on that method.

    var task = new BitmapDownloaderTask(vh.person_photo);
    vh.person_photo.SetImageDrawable(new DownloadedDrawable(task, Color.Gray));
    task.Execute(persons[position].Images[0].URL);

    Can you please help me out.

    1. Thanks Ramakrishna.

      If you are looking to use the cache within your adapter, then your adapter class will need a private member variable such as…

      private readonly MemoryLimitedLruCache _memoryCache;

      You could instantiate the cache object within the adapter’s constructor using the creation code from the article above.

      Using the cache with a BitmapDownloaderTask can be done. You could pass a reference to the cache into each task instance. Within the task, you’d need to check for the existence of a bitmap in the cache before downloading it, and add bitmaps to the cache in OnPostExecute.

    1. Hi Iman,

      In order to work with the MemoryLimitedLruCache class defined above, bitmap here would be any instance of a Bitmap image you wish to cache.

      Hope that helps!

  2. Thanks to reply.
    I have another question . I used the first part of your article and it worked fine . Now i need to cache images from listview adapter .
    I put –>> if (_memoryCache.Get(key) == null)
    _memoryCache.Put(key, bitmap);
    Into RunInBackground method . and get the cached images in listview adapter like this:
    var imageView = _view.FindViewById(Resource.Id.imgAds);
    var cmemoryCache = _memoryCache.Get(_PodCast.ImageUrl);
    if (cmemoryCache == null)
    {
    var task = new BitmapDownloaderTask(imageView);
    imageView.SetImageDrawable(new BitmapDownloaderTask.DownloadedDrawable(task, olor.Beige));
    task.Execute(_PodCast.ImageUrl);
    }
    else
    {
    imageView.SetImageBitmap((Bitmap)cmemoryCache);
    }
    And RunInBackground method :

    protected override Bitmap RunInBackground(params string[] @params)
    {
    // Get max available VM memory, exceeding this amount will throw an OutOfMemory exception.
    // Stored in kilobytes as LruCache takes an int in its constructor.
    var maxMemory = (int)(Java.Lang.Runtime.GetRuntime().MaxMemory() / 1024);

    // // Use 1/8th of the available memory for this memory cache.
    int cacheSize = maxMemory / 8;
    _memoryCache = new MemoryLimitedLruCache(cacheSize);

    Url = @params[0];
    Bitmap bitmap= DownloadRemoteImage(Url);
    _memoryCache.Put(Url, bitmap);
    return bitmap;
    }

    And Listview Adapter constructor:
    public PodCastAdapter(Activity context)
    {
    _context = context;
    _list = db.GetAllPodCast().OrderByDescending(p=>p.PodCastId).ToArray();
    // Get max available VM memory, exceeding this amount will throw an OutOfMemory exception.
    // Stored in kilobytes as LruCache takes an int in its constructor.
    var maxMemory = (int)(Java.Lang.Runtime.GetRuntime().MaxMemory() / 1024);
    // // Use 1/8th of the available memory for this memory cache.
    int cacheSize = maxMemory / 8;
    _memoryCache = new MemoryLimitedLruCache(cacheSize);
    }
    But no image caching . What am i wrong?

  3. Thanks . Now it worked for me .
    I just delete any code related to lrucache in Run method and change GetView method of Listview Adapter to :
    Bitmap bitmap= task.Execute(_PodCast.ImageUrl).GetResult();
    _memoryCache.Put(_PodCast.ImageUrl, bitmap);
    and it worked and cached images .

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s

Create a website or blog at WordPress.com

Up ↑

%d bloggers like this: