Bitmaps in Android ListView

Photocalypse

A simple task such as displaying your gallery photos in a ListView component can lead to a significant loss of performance in your Android application, to the point where it would render the application
unusable. Worse yet, the application might run out of memory and crash while loading bitmaps. Just think about user quickly scrolling through a list of hundreds of photos each the size of 3-10 megabytes.
Thus, to display pictures in ListView is not straightforward and requires several performance considerations.

Caching Bitmaps

It doesn’t make sense for us to keep default ListView behavior which continuously frees up and loads our bitmaps when our users scroll back and forth through the list.
Caching allows us to store bitmaps in memory and quickly retrieve them on consecutive views while scrolling. Thankfully, Android provides us with
LRUCache that helps us cache our bitmaps. Thus, we can create a fairly simple class for our LRUCache.

importandroid.graphics.Bitmap;importandroid.support.v4.util.LruCache;publicclassBitmapCache{privatestaticLruCache<Integer,Bitmap>MemoryCache=null;publicstaticvoidInitBitmapCache(){// Get max available VM memory, exceeding this amount will throw an// OutOfMemory exception. Stored in kilobytes as LruCache takes an// int in its constructorif(MemoryCache==null){finalintmaxMemory=(int)(Runtime.getRuntime().maxMemory()/1024);// use 1/4th of the available memory for this memory cache.finalintcacheSize=maxMemory/4;MemoryCache=newLruCache<Integer,Bitmap>(cacheSize){c@OverrideprotectedintsizeOf(Integerkey,Bitmapbitmap){// The cache size will be measured in kilobytes rather than// number of items.returnbitmap.getByteCount()/1024;}};}}publicstaticvoidaddBitmapToMemoryCache(Integerkey,Bitmapbitmap){MemoryCache.put(key,bitmap);}publicstaticBitmapgetBitmapFromMemCache(Integerkey){returnMemoryCache.get(key);}}

The above code allows creation of LruCache with a quarter of available memory, and adding and retrieving bitmaps by key value, which is a position of an image in our ListView.

Resampling Bitmaps

Loading hundreds of full-sized image bitmaps would quickly cause our application to crash from lack of memory. Oftentimes, displaying image at fraction of the original size in a list is sufficient.
The functions below help us resize the image and resample the bitmap before using it further.

Basically, calculateInSampleSize takes original image width or height and divides them by the target width or height. Returned inSampleSize then gives us a divisor to create processed bitmaps that are a
fraction of the original size. Finally, decodeSampledBitmapFromString reads our bitmap from provided image path and returns resized smaller bitmap.

Loading Asynchronously

Initially, we want to load all our bitmaps in a separate thread or process, so we do not freeze our UI thread. To achieve that, we can use
AsyncTask. In our case, this is also where we want to re-sample our bitmaps and place the images into cache. Therefore,
our AsyncTask would look like this:

publicclassImageLoaderTaskextendsAsyncTask<Integer,String,Bitmap>{...@OverrideprotectedBitmapdoInBackground(Integer...params){//re-sample sample image in the background to 200x200Bitmapbitmap=decodeSampledBitmapFromString(200,200);}//set photoView and holderprotectedvoidonPostExecute(Bitmapbitmap){if(bitmap!=null){BitmapCache.addBitmapToMemoryCache(m_position,bitmap);// to do: set imageView }}privateBitmapdecodeSampledBitmapFromString(intreqWidth,intreqHeight){...}privateintcalculateInSampleSize(BitmapFactory.Optionsoptions,intreqWidth,intreqHeight){...}}

Putting it all together

In the main activity, we would initialize our bitmap cache in onCreate() method:

Then, in our ListView adapter, we would add loadBitmap() function to either set ImageViews bitmaps from cache or to start a new AsyncTask.

publicclassGalleryAdapterextendsBaseAdapter{...@OverridepublicViewgetView(intposition,Viewview,ViewGroupparent){...loadBitmap(photo,position,holder);}// load Bitmap either from our cache or asynchronouslypublicvoidloadBitmap(ImageViewphoto,Integerposition,ViewHolderholder){Bitmapbitmap=null;bitmap=BitmapCache.getBitmapFromMemCache(position);if(bitmap!=null){photo.setImageBitmap(bitmap);}else{newImageLoaderTask(photo,m_uris.get(position).getPath(),m_adapter,position,holder).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,(Integer[])null);}}

Also, we could also do other things to optimize performance, such as implementing ViewHolder Pattern to recycle views
in a static class. In our example, ViewHolder will hold two things: ImageView and its position in ListView.

Finally, Android has a limit of 128 simultaneous AsyncTasks and gives an error if we exceed that limit. This error is entirely possible when loading a thousand bitmaps in a ListView.
One way to prevent the error from happening and to manage memory is to implement a static class to keep and control current AsyncTask count.

//static class to count number of concurrent asynctaskspublicfinalclassSyncCounter{privatestaticinti=0;publicstaticsynchronizedvoidinc(){i++;}publicstaticsynchronizedvoiddec(){i--;}publicstaticsynchronizedintcurrent(){returni;}}

Our AsyncTask class could then increment the count in the AsyncTask constructor and decrement in onPostExecute() method. We would check the current count against arbitrary maximum
prior to launching new ImageLoaderTask(...) in List Adapter loadBitmap(...) method.