Trifork Blog

AngularJS training

Exploring the world of Android :: Part 2

September 17th, 2009 by
| Reply

And I'm back! Reporting live on the glorious adventures in the exciting world of Android. This blog post is the second one in the Android series. This time with code samples! Yeah!

In my first blog post about Android I talked about setting up a project using Android. This time I want to discuss a more "advanced" topic: ListView performance. A ListView is a view component which allows you to display and scroll through a list of items. It can display simple text in each row, but is also able to display a more complicated structure. In the latter case you will need to make sure your ListView still performs well (read: renders fast and scrolls smoothly). I am going to provide solutions to a few different performance problems when using a ListView

The ListAdapter

If you want to use a ListView, you will have to supply it with a ListAdapter to allow it to display any content. A few simple implementations of that adapter are already available in the SDK:

These implementations are perfect for displaying very simple lists. But if your list is just a little more complicated than that, you will need to write your own custom ListAdapter implementation. In most cases it's useful to subclass ArrayAdapter which already takes care of managing a list of objects. Now you only have to tell it how to render each object in the list. Do this by overriding the getView(int, View, ViewGroup) method of the ArrayAdapter class.

A simple example

To give you a simple example of a case in which you need to write your own ListAdapter: displaying a list of images with some text next to it.

images with text in a ListView
Example of a ListView containing Youtube search results in the form of images and text

The images need to be on-the-fly downloaded from the internet. Let's create a class which represents items in the list:

public class ImageAndText {
    private String imageUrl;
    private String text;

    public ImageAndText(String imageUrl, String text) {
        this.imageUrl = imageUrl;
        this.text = text;
    }
    public String getImageUrl() {
        return imageUrl;
    }
    public String getText() {
        return text;
    }
}

Now, let's create an implementation of a ListAdapter that is able to display a list of these ImageAndTexts.

public class ImageAndTextListAdapter extends ArrayAdapter<ImageAndText> {

    public ImageAndTextListAdapter(Activity activity, List<ImageAndText> imageAndTexts) {
        super(activity, 0, imageAndTexts);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Activity activity = (Activity) getContext();
        LayoutInflater inflater = activity.getLayoutInflater();

        // Inflate the views from XML
        View rowView = inflater.inflate(R.layout.image_and_text_row, null);
        ImageAndText imageAndText = getItem(position);

        // Load the image and set it on the ImageView
        ImageView imageView = (ImageView) rowView.findViewById(R.id.image);
        imageView.setImageDrawable(loadImageFromUrl(imageAndText.getImageUrl()));

        // Set the text on the TextView
        TextView textView = (TextView) rowView.findViewById(R.id.text);
        textView.setText(imageAndText.getText());
        
        return rowView;
    }

    public static Drawable loadImageFromUrl(String url) {
        InputStream inputStream;
        try {
            inputStream = new URL(url).openStream();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return Drawable.createFromStream(inputStream, "src");
    }
}

The views are inflated from an XML file called "image_and_text_row.xml":

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="horizontal"
              android:layout_width="fill_parent"
              android:layout_height="wrap_content">

        <ImageView android:id="@+id/image"
                   android:layout_width="wrap_content"
                   android:layout_height="wrap_content"
                   android:src="@drawable/default_image"/>

        <TextView android:id="@+id/text"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"/>

</LinearLayout>

This ListAdapter implementation renders the ImageAndTexts in the ListView like you would expect. The only thing is that this only works for a very small list which doesn't require scrolling to see all items. If the list of ImageAndTexts gets bigger you will notice that scrolling isn't as smooth as it should be (in fact, it's far off!).

Improving performance

The biggest bottleneck in the above example is the fact that the images have to be downloaded from the internet. Because we execute all our code in the same thread as the UI, the UI will get stuck each time an image is being downloaded. If you run the same application using a 3G internet connection instead of WiFi, the performance will even be worse.

To avoid this we want the image to be loaded in a separate thread to not disturb the UI thread too much. To make this happen, we could use an AsyncTask which is designed for cases like this. But in practice, you will notice that the AsyncTask is limited to 10 threads. This number is hardcoded somewhere in the Android SDK so we cannot change this. In this case it's a limitation we cannot live with, because often more than 10 images are loaded at the same time.

AsyncImageLoader

An alternative is to manually spawn a new Thread for each image. In addition we should use Handlers to deliver the downloaded images to the UI thread. We want to do this because only from the UI thread you are allowed to modify the UI (read: draw an image on the screen). I created a class called AsyncImageLoader which takes care of loading images using Threads and Handlers like I just described. Also it caches images to avoid a single image to be downloaded multiple times.

public class AsyncImageLoader {
    private HashMap<String, SoftReference<Drawable>> imageCache;

    public AsyncImageLoader() {
    	drawableMap = new HashMap<String, SoftReference<Drawable>>();
    }

    public Drawable loadDrawable(final String imageUrl, final ImageCallback imageCallback) {
    	if (drawableMap.containsKey(imageUrl)) {
            SoftReference<Drawable> softReference = imageCache.get(imageUrl);
            Drawable drawable = softReference.get();
            if (drawable != null) {
                return drawable;
            }
    	}
    	final Handler handler = new Handler() {
    		@Override
    		public void handleMessage(Message message) {
                imageCallback.imageLoaded((Drawable) message.obj, imageUrl);
    		}
    	};
    	new Thread() {
    		@Override
    		public void run() {
                Drawable drawable = loadImageFromUrl(imageUrl);
                imageCache.put(imageUrl, new SoftReference<Drawable>(drawable));
                Message message = handler.obtainMessage(0, drawable);
                handler.sendMessage(message);
    		}
    	}.start();
        return null;
    }

    public static Drawable loadImageFromUrl(String url) {
        // ...
    }

    public interface ImageCallback {
        public void imageLoaded(Drawable imageDrawable, String imageUrl);
    }
}

Notice that I used a SoftReference for caching images, to allow the garbage collector to clean the images from the cache when needed. How it works:

  • Call loadDrawable(imageUrl, imageCallback) providing an anonymous implementation of the ImageCallback interface
  • If the image doesn't exist in the cache yet, the image is downloaded in a separate thread and the ImageCallback is called as soon as the download is complete.
  • If the image DOES exist in the cache, it is immediately returned and the ImageCallback is never called.

Only one instance of AsyncImageLoader should exist in your application, or else the caching won't work. If we take the example ImageAndTextListAdapter class we can now replace:

ImageView imageView = (ImageView) rowView.findViewById(R.id.image);
imageView.setImageDrawable(loadImageFromUrl(imageAndText.getImageUrl()));

with:

final ImageView imageView = (ImageView) rowView.findViewById(R.id.image);
Drawable cachedImage = asyncImageLoader.loadDrawable(imageAndText.getImageUrl(), new ImageCallback() {
    public void imageLoaded(Drawable imageDrawable, String imageUrl) {
        imageView.setImageDrawable(imageDrawable);
    }
});
imageView.setImageDrawable(cachedImage);

Using this approach, the ListView performs a lot better and feels much more smooth because the UI thread is no longer blocked by the loading of images.

Improve the performance even more

If you tried the solution described above you will notice that the ListView is still not a 100% smooth. You will still notice some little disruptions that make it a little less smooth than it could be. There are two things remaining that can be improved:

  • the expensive call to findViewById()
  • inflating the entire row from XML every time

The solution is obvious: we should cache/reuse these things! Mark Murphy did a very nice job on writing a few blog entries describing how this can be done. To reuse the views which are inflated from XML read this blog entry:
http://www.androidguys.com/2008/07/17/fancy-listviews-part-two/

To cache the views returned by findViewById() read this blog entry:
http://www.androidguys.com/2008/07/22/fancy-listviews-part-three/

If we apply the strategies described in Mark Murphy's blog entries our ImageAndTextListAdapter could look like the following:

public class ImageAndTextListAdapter extends ArrayAdapter<ImageAndText> {

    private ListView listView;
    private AsyncImageLoader asyncImageLoader;

    public ImageAndTextListAdapter(Activity activity, List<ImageAndText> imageAndTexts, ListView listView) {
        super(activity, 0, imageAndTexts);
        this.listView = listView;
        asyncImageLoader = new AsyncImageLoader();
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Activity activity = (Activity) getContext();

        // Inflate the views from XML
        View rowView = convertView;
        ViewCache viewCache;
        if (rowView == null) {
            LayoutInflater inflater = activity.getLayoutInflater();
            rowView = inflater.inflate(R.layout.image_and_text_row, null);
            viewCache = new ViewCache(rowView);
            rowView.setTag(viewCache);
        } else {
            viewCache = (ViewCache) rowView.getTag();
        }
        ImageAndText imageAndText = getItem(position);

        // Load the image and set it on the ImageView
        String imageUrl = imageAndText.getImageUrl();
        ImageView imageView = viewCache.getImageView();
        imageView.setTag(imageUrl);
        Drawable cachedImage = asyncImageLoader.loadDrawable(imageUrl, new ImageCallback() {
            public void imageLoaded(Drawable imageDrawable, String imageUrl) {
                ImageView imageViewByTag = (ImageView) listView.findViewWithTag(imageUrl);
                if (imageViewByTag != null) {
                    imageViewByTag.setImageDrawable(imageDrawable);
                }
            }
        });
        imageView.setImageDrawable(cachedImage);

        // Set the text on the TextView
        TextView textView = viewCache.getTextView();
        textView.setText(imageAndText.getText());
        
        return rowView;
    }
}

There are two things to notice. The first thing is that the drawable is not directly set to the ImageView anymore after loading. Instead, the right ImageView is looked up through it's tag. This is done because we're now reusing views and the images might end up on the wrong rows. We need a reference to the ListView to lookup ImageViews by tag.

The other thing to notice, is that this implementation uses an object called ViewCache. This is what the class for that object looks like:

public class ViewCache {

    private View baseView;
    private TextView textView;
    private ImageView imageView;

    public ViewCache(View baseView) {
        this.baseView = baseView;
    }

    public TextView getTextView() {
        if (textView == null) {
            textView = (TextView) baseView.findViewById(R.id.text);
        }
        return titleView;
    }

    public ImageView getImageView() {
        if (imageView == null) {
            imageView = (ImageView) baseView.findViewById(R.id.image);
        }
        return imageView;
    }
}

This ViewCache is the same as what Mark Murphy calls a "ViewWrapper" and takes care of caching individual views which normally would have to be looked up every time using the expensive call to findViewById().

To summarize

I've shown you how to improve performance of a ListView in three different ways:

  • By loading images in a seperate thread
  • By reusing rows in the list
  • By caching views within a row

It took me quite some time to figure this stuff out, especially the image loading part. So I thought it is all worth mentioning to avoid you having to waste too much time on it.

Next time I will discuss other interesting challenges in the world of Android!

TO BE CONTINUED ...

29 Responses

  1. September 17, 2009 at 10:50 by Mark Murphy

    Thanks for the shout-outs! Or, the shouts-out! Oh, thanks for linking to me!

    ;-)

    Also, it's good to see somebody else blogging about Android development!

    One quick warning though: while your use of SoftReferences should work, there are issues with the Android garbage collector that prevent SoftReferences from being effective. I do not know when those issues will be resolved. The net is that your SoftReferences may be removed too soon due to over-aggressive memory reclamation. For a similar image caching mechanism I implemented, I wound up using LRU caching via a LinkedHashMap to get past this issue. You may want to hunt through the [android-developers] list for mentions of SoftReference -- fadden and others discussed this a couple of months ago.

  2. September 18, 2009 at 14:06 by Tom van Zummeren

    Hey Mark, thanks for the warning about SoftReferences!

    Although I didn't encounter any issues with using SoftReferences yet myself. In my app it doesn't seem to collect the images too soon like you're describing. In my app it only happens when I scroll all the way down a list and scroll all the way up again. In that case you sometimes see maybe 2 or 3 images being reloaded at the top of the list.

  3. October 22, 2009 at 10:32 by Ramesh Rao

    I have tried your solution but works for me only for Listview's with lesser number of Cells when the cells are greater or equal to 20 and with all the scrolling and everything things tend to take ugly face.

  4. November 18, 2009 at 01:47 by Trevis

    This is a good topic but I don't like the way it is set up. The first part series starts out simple with creating a project. However, part2 gets into ListView, but you don't show alot of the code. For example, how you do implement this ArrayAdapter in the onCreate Method. Yes, I am a beginner dev.

  5. December 4, 2009 at 15:36 by Samuh

    First of all, thanks to you and Mark Murphy for posting such wonderful tutorials.

    I tried your code without setting a default image in the ImageView and observed that when the list is first loaded, I see about 10 rows, some with loaded images and some without. If I do not scroll the list (so as to eliminate the chance of a view repaint) the view is never refreshed and so the newly loaded images do not make it to the screen.

    Is there a way to auto-refresh the list(without user scrolling it)? I was assuming that setting the actual image in the callback will invalidate the view and the list will refresh automatically, but it seems I was wrong.

  6. December 9, 2009 at 19:46 by Tarquinio

    Why do you have two HasMaps (imageCache and drawableMap)?

  7. January 12, 2010 at 16:37 by JohnnyB

    Thanks for this nice tutorial.

    This may be a noob question but isn't it necessary for your class to extend ListActivity and call setListAdapter with the custom adapter?

  8. February 19, 2010 at 07:53 by Bara

    Took me forever to figure this out, but there should only be one HashMap: imageCache. DrawableMap must have been old code or he renamed it or something, but rename all instances of drawableMap to imageCache in the code above.

  9. March 26, 2010 at 00:40 by genXsol

    Very useful tutorial but needs some more tweaking to get it work properly.
    i dont really know how to improve it but expecting someone must have resolved the issue of disappearing images on scrolling.

    Yes, i am having the same problem, it loads the rows of current screen and i have to keep scrolling up and down until it loads all the images. sometime even loaded image just disappears.
    all images seems to be cached in drawableMap but doesn't showup on view sometime.
    is there any way i can resolve this issue? shall i keep them on sdcard rather than in map or better to use FastDrawable solution as photostream does?
    can anybody direct me to the right direction of how to make listAdapter smooth with async image loading and caching?

  10. [...] and am sharing the code below in the hopes that this will help. While, this is vastly derived from Tom van Zummeren’s tutorial at jTeam, but the key that made this work right was recognizing from Nazmul’s tutorial at [...]

  11. [...] notice the use of SoftReference here, which I gleaned from Tom van Zummeren’s tutorial. While this appears to work well, I haven’t done any significant load or performance testing, [...]

  12. [...] http://blog.jteam.nl/2009/09/17/exploring-the-world-of-android-part-2/  Tom van Zummerenhttp://evancharlton.com/thoughts/lazy-loading-images-in-a-listview/ Evan Charlton [...]

  13. [...] of images in the list view. For implementing this i was referring to post by Tom van Zummeren. (http://blog.jteam.nl/2009/09/17/exploring-the-world-of-android- part-2/), but my images keep on changing in the list. Please can anyone let me know if they were able to [...]

  14. July 2, 2010 at 14:03 by tabman

    Is there a limit to how many threads this approach can spawn at a time ? e.g. If there are 500 images in a ListView ?

  15. July 2, 2010 at 16:47 by tabman

    I implemented your algorithm in my application and I have nearly 500 thumbnails in a gridview.

    As I expected if I fling I get OutOfMemory exception. I suspect cause when the app is flinging very quickly the pace of new threads created is not matched with the pace of garbage collection ?

    If I scroll slowly then there is no problem as I think it gives time to the VM to do initialization and collection.

    Would like to know your thoughts.

  16. September 4, 2010 at 15:07 by Ryan

    Sorry, I have no idea how to use these class to implement a ListView such as the first graph.
    Could you demonstrate a sample Activity?

  17. October 28, 2010 at 01:08 by Avi

    ImageView imageViewByTag = (ImageView) listView.findViewWithTag(imageUrl);

    What is this listView?
    Is it the listview that contains the list (each one of the rows we inflate)?
    Or is it something else?
    Is there any chance of a demo for the last part("IMPROVE THE PERFORMANCE EVEN MORE")?
    i implemented all the rest parts.

    Thanks in advanced.

  18. November 4, 2010 at 03:27 by bjc

    drawableMap is not necessary. I replaced the two references to that to imageCache, and this works better. In fact, I believe that was the original intent of the author.

  19. November 19, 2010 at 07:42 by sneha

    **Here is the easier code for creating custom list-view with different images*

    which looks something like this image

    ![alt text][1]

    firstly starts with creating UI....................
    1.main.xml
    2.row.xml
    and add suitable images that you want to embed in your application

    then start your main List Activity java class
    3.Navigation.java

    here is the main.xml

    ----------

    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content">

    now here is the code for Listview Images

    row.xml

    ----------

    now the main ListActivity java class where we have to create ListAdapter that manages a ListView backed by an array of arbitrary objects.

    Navigation.java

    package com.exercise.Navigation;
    import com.exercise.Navigation.R;

    import android.app.ListActivity;
    import android.content.Context;
    import android.os.Bundle;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.ArrayAdapter;
    import android.widget.ImageView;
    import android.widget.ListView;
    import android.widget.TextView;
    import android.widget.Toast;
    public class Navigation extends ListActivity
    {public class MyCustomAdapter extends ArrayAdapter

    {
    public MyCustomAdapter(Context context, int textViewResourceId, String[] objects)
    { super(context, textViewResourceId, objects);

    }
    @Override
    public View getView(int position, View convertView, ViewGroup parent)
    {

    LayoutInflater inflater=getLayoutInflater();
    View row=inflater.inflate(R.layout.row, parent, false);
    TextView label=(TextView)row.findViewById(R.id.navigation_item);
    label.setText(item[position]);
    ImageView icon=(ImageView)row.findViewById(R.id.icon);
    if (item[position]=="Notes")
    {
    icon.setImageResource(R.drawable.notes);
    }
    if(item[position]=="H and P")
    {
    icon.setImageResource(R.drawable.hnp);

    }
    if(item[position]=="Imaging")
    {
    icon.setImageResource(R.drawable.imaging);

    }
    if(item[position]=="Vital")
    {
    icon.setImageResource(R.drawable.vital);

    }
    if(item[position]=="Problem")
    {
    icon.setImageResource(R.drawable.problem);

    }
    if(item[position]=="ADT")
    {
    icon.setImageResource(R.drawable.adt);

    }
    if(item[position]=="Notification")
    {
    icon.setImageResource(R.drawable.notification);
    }
    if(item[position]=="ECG")
    {
    icon.setImageResource(R.drawable.ecg);
    }
    return row;}}
    String[] item = {"Notes","H and P","Imaging","Vital","Problem","ADT","Notification","ECG"};

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
    super.onCreate(savedInstanceState);

    setListAdapter(new MyCustomAdapter(Navigation.this, R.layout.row, item));
    }
    @Override
    protected void onListItemClick(ListView l, View v, int position, long id)
    {

    String selection = l.getItemAtPosition(position).toString();
    Toast.makeText(this, selection, Toast.LENGTH_LONG).show();
    }

    }

    [1]: http://i.imgur.com/L78k5.png

  20. [...] menu’s and even drawing custom application/tab icons in Photoshop! Some challenges I already shared with you guys, but there is one challenge in particular I would like to elaborate on this [...]

  21. February 14, 2011 at 12:34 by Juan Diego Pérez

    Great tutorial....but two question (my java is a bit stained)

    Which is the function of the callback? I have removed that part and everything works well.

    can i wait for every image to download?

    Thanks

  22. [...] also researched this topic. According to Mark Murphy in this article, it seems that there is (or was?) a bug with the SoftReference. Some other results indicates the [...]

  23. August 7, 2011 at 20:03 by cp

    Very nice approach of lazy loading images in a listview. The only thing that i have not figure out is that the default image (@drawable/default_image) is never shown. I have tried to set programatically but no results. Any ideas?

  24. January 24, 2012 at 11:06 by Shubham Goyal

    Thanks! Very useful post for me. Exactly got what I was looking for.
    Great blogging.. Keep it up.

  25. July 16, 2012 at 15:27 by mike

    Hi great tutorial:) I have to make an application which is much like yours,meaning i need to have an image on the left and text on the right.But i have to write also the web service that will provide my app with the pictures.So my question is when you used the function loadImageFromUrl(String url) it would download you a certain image right?Was that image byte[] and then you transformed into Image? or something entirely different?

  26. December 6, 2012 at 06:30 by kedar

    can anyone of you please explain what is happening in AsyncImageLoader class ??

  27. [...] Tom van Zummeren’s implementation of similar component with several well known problems [...]

  28. [...] Tom van Zummeren’s implementation of similar component with several well known problems [...]

  29. February 6, 2013 at 18:44 by swapnil

    I have tried this but not able to get the out out...i am trying the first example. I have set the values for image url and the text. I have 10 such values which i have added in an List...how do i pass it to the Adapter

Leave a Reply