This is the first in a series on image handling in Android. It is specific to Xamarin, but the principles discussed here apply to Java-based Android apps as well. This article assumes prerequisite knowledge of ListViews in Android, and the use of Adapters to supply the data they display.
There are a number of complications to consider when loading images, some of which are unique to Xamarin, and others are universal.
- UI Responsiveness
- Image resizing/sampling
- Image Caching
- Bitmap Garbage Collection
In this first post, I will address UI Responsiveness, and build some classes which can be used to make your lists speedy, even with hundreds or even thousands of image-based items.
Generally, Android apps need images in order to look interesting, and a quick look through the stock Android apps will make this plain. Imagine if the Android image gallery showed a list of image names instead of a thumbnail preview of your photos!
I have recently been working on an app in which a large list of products is displayed, and each needed to show a thumbnail image. There were many thousands of products, so the images are not embedded in the app; they are retrieved from the internet.
Android does an excellent job of keeping ListViews responsive. It loads views for each item into memory as and when it needs to, and recycles old views to allow us to reuse them. Considering it may take some time to download the remote images for use in the list, we must perform all image retrieval on a background thread. This keeps the UI thread clear, and allows the user to interact with the list. When each image is loaded, it can be added to an ImageView in its list item. Xamarin outline one way to perform background tasks using the ThreadPool. My first implementation of the list queued up a task for each list item in this fashion. This worked fine, but there was a long wait (around a second) for the first image to start loading. The waiting time is due to the ThreadPool being dynamically created the first time it’s used. The total number of threads, and the creation of new threads is also throttled by the .Net framework, so there was a short delay between each download. These delays make for a sluggish appearance, even on fast connections. I’m sure that fiddling with the ThreadPool settings may have yielded some results, however, Android provides a number of mechanisms for threading, and they are highly optimised for running on devices with limited resources.
One such mechanism is the AsyncTask class. This provides methods for performing tasks on a background thread, and publishing the results on the UI Thread when ready. Without any fiddling, the class is very performant, and will work well on slower devices. AsyncTask must be subclassed to be used, but it’s a simple job to create a bitmap downloader task which will fetch an image in the background, and then update an ImageView when it’s done. The good thing about the AsyncTask class is that we don’t need to worry about the ins and outs of threading, as it will take care of the heavy lifting for us. Simply override the
doInBackground() method for the background work, and the
onPostExecute() method to update the UI.
Xamarin Tip: Because of the way Xamarin maps its classes onto their Java equivalents, the names of methods aren’t always the same as the Java documentation. Usually, it’s a matter of changing the casing to conform with C# conventions, i.e.
DoInBackground(). In this case however the
doInBackgroundmethod is called
Here is an example implementation of the AsyncTask class for use with Xamarin. Please note, that the class may not work properly for images within a list, due to an idiosyncrasy of the ListView in Android. I’ll explain the problem and how to fix it below.
This class is used as follows.
This will create a new BitmapDownloaderTask, and execute it with the supplied remote url. When the image has finished downloading, it will be assigned to the Imageview.
I mentioned earlier that this class may not work for Lists in Android. This is due to the way that Android recycles its views. When a view for a list item leaves the screen, Android keeps hold of it, and allows us to reuse it. While this allows Android to display very long lists without needing a lot of memory, it presents a problem when working asynchronously, as we wish to here.
For example, if you fire off an async request for an image while a user is scrolling, it’s possible that the list item will not be on screen when the image has finished loading. If you’re recycling your views properly, then the view may be in use, and back on screen for a different list item when the image finishes loading. The image may show up briefly before the correct image shows, or worse, it might take longer than the correct one, and replace it! If you’re not recycling your views for some reason, then you may be keeping images in memory which aren’t on-screen.
We can fix all the above issues by modifying our code slightly. Put simply, if the BitmapDownloaderTask isn’t the latest one which was fired up for a particular ImageView, don’t assign the bitmap. To do this we need a way to associate each download task with its ImageView. If Android comes along and reuses a list item (which contains our ImageView), and another async task is assigned to it, then the first task won’t bother with it’s final work.
One nice way to associate an ImageView with a BitmapDownloaderTask is to create a custom Drawable. We can assign this drawable to an image view, and then use it later from within BitmapDownloaderTask to check it’s the same ImageView which started the task. This also allows us to show a loading icon, or in as in the example below, just show a colour.
This class has a method called
GetBitmapDownloaderTask(), which we can call to get the associated BitmapDownloaderTask reference.
To make things cleaner, I’ve created an extension method which can be used to try to get an associated task from an ImageView.
Now we can modify our original class to use the functionality above.
Now the task uses the
GetBitmapDownloaderTask() method to confirm that the ImageView which initialised it is still the correct view, and therefore hasn’t been reused by a new async task. It only assigns the bitmap to the view if it’s the correct view.
Another key change here is the use of a weak reference within the
BitmapDownloaderTask class for the ImageView. The ImageView will, when loaded, reference bitmap data in the Java runtime. Once the view drops off the screen, the weak reference allows the ImageView to be garbage collected to free up memory if required. I’ll detail the ins and outs of garbage collection in Xamarin in a future post.
This should perform well, but of course there are ways it can be improved further if needed. Any async tasks which are no longer in use can be cancelled. Multiple requests for the same url should not be performed. Bitmaps which are larger than needed can be reduced in size, and sampled down. Bitmaps can also be cached, so that they are not requested multiple times unless required. The next article will deal with bitmap caching in detail.