Tuesday, July 3, 2012

Displaying Bitmaps Efficiently in Android



Before read this blog post, I highly recommend you to read the original article (Display Bitmaps Efficiently) from Android Developer website first. This is just my summary and diagrams depict the ideas from the original article.


To load Bitmap into memory in an efficient way, you will need to do 2 steps.

  1. Read Bitmap dimensions.
  2. Load a scaled down version into memory.

Here is a flowchart depicts the step to load Bitmap in an efficient way described in the Android Training document.

Fig.1 Steps to load Bitmap.


Processing Bitmaps Off the UI Thread

Processing Bitmap should be done in the worker Thread. The topic "Processing Bitmaps off the UI Thread" describes and show how to process Bitmap using AsyncTask. In the tutorial, each ImageView is referred using WeakReference inside the BitmapWorkerTask. Below is the diagram showing the relationship between BitmapWorkerTask and ImageView from the tutorial.


Fig.2 Relationship between BitmapWorkerTask and ImageView from the tutorial.

The interesting point from this topic is the way to handle concurrency issue. Using AsyncTask to process Bitmaps inside GridView or ListView  can cause ImageView to display wrong image when user scrolls the View. The idea to fix this problem is simple, when got ImageView from the adapter callback, simply check that this ImageView has a BitmapWorkerTask refers to it or not. If there is a BitmapWorkerTask refers to this ImageView then cancel the task before begin process new Bitmap.

The way to check that each ImageView has BitmapWorkerTask refers to it or not is wrapping the BitmapWorkerTask inside the dedicated Drawable subclass (the original article names this class "AsyncDrawable") then set the AsyncDrawable to the ImageView.


Fig.3 Relationship between BitmapWorkerTask, ImageView and AsyncDrawable.

Now when got ImageView from system callback (getView() method), we can check that this ImageView has BitmapWorkerTask refers to it or not, if it is, just simply cancel this task if it is not process the same Bitmap resource with the current request.


Fig.4 Flowchart illustrates the procedure to cancel an exiting BitmapWorkerTask before processing new Bitmap.

Also in BitmapWorkerTask, when processing Bitmap is finished, check if this task is cancelled or the current ImageView is already referred by another task (recycled) before setting image.


Fig.5 Flowchart illustrates the procedure to check current BitmapWorkerTask state before setting new image.


In case of the View components like GridView or ListView, when user scrolls back to the view that already recycled, you have to reprocess the Bitmap for that view again. Reprocess the Bitmap every time user scrolls back to the view cause your UI less responsive. Instead of reprocess, using cache is a better solution.

There are two types of cache: memory cache and disk cache.

For memory cache, LruCache (Least Recently Used Cache) was introduced in Android 3.1 (API level 12) and also available in support package for using in earlier Android version.

For disk cache, an example DiskLruCache class is included inside bitmap sample project and also in Android 4.0 source code (libcore/luni/src/main/java/libcore/io/DiskLruCache.java )

In the case of memory cache, the size of cache is very important. Please see this document for more information.

No comments:

Post a Comment