Phil
Phil

Reputation: 5615

Safe, standard way to load images in ListView on a different thread?

Before making this question, I have searched and read these ones: Lazy load of images in ListView Android - Issue with lazy loading images into a ListView

My problem is I have a ListView, where:

What I want ultimately:

My current approach:

Below is somewhat a pseudocode for my current approach. I've ommitted other details like caching, just to keep the thing clear.

public class ImageLoader {

    // keeps track of the last Bitmap we want to set for this ImageView
    private static final WeakHashMap<ImageView, AsyncTask> assignments
                                    = new WeakHashMap<ImageView, AsyncTask>();

    /** Asynchronously sets an ImageView to some Bitmap loaded from the internet */
    public static void setImageAsync(final ImageView imageView, final String imageUrl) {
        // cancel whatever previous task
        AsyncTask oldTask = assignments.get(imageView);
        if (oldTask != null) {
            oldTask.cancel(true);
        }

        // prepare to launch a new task to load this new image
        AsyncTask<String, Integer, Bitmap> newTask = new AsyncTask<String, Integer, Bitmap>() {

            protected void onPreExecute() {
                // set ImageView to some "loading..." image
            }

            protected Bitmap doInBackground(String... urls) {
                return loadFromInternet(imageUrl);
            }

            protected void onPostExecute(Bitmap bitmap) {
                // set Bitmap if successfully loaded, or an "error" image
                if (bitmap != null) {
                    imageView.setImageBitmap(bitmap);
                } else {
                    imageView.setImageResource(R.drawable.error);
                }
            }
        };
        newTask.execute();

        // mark this as the latest Bitmap we want to set for this ImageView
        assignments.put(imageView, newTask);
    }

    /** returns (Bitmap on success | null on error) */
    private Bitmap loadFromInternet(String imageUrl) {}
}

Problem I still have: what if the Activity gets destroyed while some images are still loading?

Anyway, I wonder if there is a safe and standard way of doing this in Android. In addition, I don't know iPhone but is there a similar problem there and do they have a standard way to do this kind of task?

Many thanks.

Upvotes: 1

Views: 2999

Answers (1)

CommonsWare
CommonsWare

Reputation: 1006594

I solve this by maintaining a WeakHashMap that remembers the last Bitmap I want to set for that view.

I took the approach attaching the the URL of the desired image onto the ImageView via setTag(). Then, when I have the image downloaded, I double-check the ImageView URL -- if it is different than the one I just downloaded, I don't update the ImageView, because it got recycled. I just cache it.

Is there any risk when the loading thread calls back to the ImageView later, when the Activity is already destroyed?

I am not aware of any risk, other than a bit of wasted CPU time and bandwidth (and, hence, battery).

Instead of making ImageLoader a singleton like this, I'm thinking of actually creating separate ImageLoader objects for different Activities, then when an Activity gets destroyed, all its AsyncTask will be canceled. Is this too awkward?

Canceling an AsyncTask is not terribly easy, if it is already running. I'd just let it run to completion.

Ideally, avoid singletons. Either use a Service, or pass your ImageLoader to the next instance of your activity via onRetainNonConfigurationInstance() (e.g., isFinishing() is false in onDestroy(), so this is a rotation).

Upvotes: 4

Related Questions