views:

18395

answers:

9

Hi

I am using a ListView to display some images and captions associated with those images. I am getting the images from the Internet. Is there a way to lazy load the images so while the text displays, the UI is not locked up and images are displayed as they are downloaded. The number of images is not fixed.

Thanks.

+3  A: 

The way I do it is by launching a thread to download the images in the background and hand it a callback for each list item. When an image is finished downloading it calls the callback which updates the view for the list item.

This method doesn't work very well when you're recycling views however.

using a thread for each image is the approach I use as well. If you separate your model from your view you can persist the model outside the Activity (like in your 'application' class) to keep them cached. Beware of running out of resources if you have many images.
James A Wilson
can you please elaborate. I am new to android development. Thanks for the tips though
lostInTransit
Starting a new thread for each image is not an effective solution. You can end up with a lot of threads in memory and freezing UI.
Fedor
Fedor, agreed, I usually use a queue and a thread pool, that's the best way to go imo.
+39  A: 

here's what I created to hold the images that my app is currently displaying. Please note that the "Log" object in use here is my custom wrapper around the final Log class inside android.

package com.wilson.android.library;

/*
 Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.    
*/
import java.io.IOException;

public class DrawableManager {
    private final Map drawableMap;

    public DrawableManager() {
     drawableMap = new HashMap();
    }

    public Drawable fetchDrawable(String urlString) {
     if (drawableMap.containsKey(urlString)) {
      return drawableMap.get(urlString);
     }

     Log.d(this.getClass().getSimpleName(), "image url:" + urlString);
     try {
      InputStream is = fetch(urlString);
      Drawable drawable = Drawable.createFromStream(is, "src");
      drawableMap.put(urlString, drawable);
      Log.d(this.getClass().getSimpleName(), "got a thumbnail drawable: " + drawable.getBounds() + ", "
        + drawable.getIntrinsicHeight() + "," + drawable.getIntrinsicWidth() + ", "
        + drawable.getMinimumHeight() + "," + drawable.getMinimumWidth());
      return drawable;
     } catch (MalformedURLException e) {
      Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
      return null;
     } catch (IOException e) {
      Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
      return null;
     }
    }

    public void fetchDrawableOnThread(final String urlString, final ImageView imageView) {
     if (drawableMap.containsKey(urlString)) {
      imageView.setImageDrawable(drawableMap.get(urlString));
     }

     final Handler handler = new Handler() {
      @Override
      public void handleMessage(Message message) {
       imageView.setImageDrawable((Drawable) message.obj);
      }
     };

     Thread thread = new Thread() {
      @Override
      public void run() {
       //TODO : set imageView to a "pending" image
       Drawable drawable = fetchDrawable(urlString);
       Message message = handler.obtainMessage(1, drawable);
       handler.sendMessage(message);
      }
     };
     thread.start();
    }

    private InputStream fetch(String urlString) throws MalformedURLException, IOException {
     DefaultHttpClient httpClient = new DefaultHttpClient();
     HttpGet request = new HttpGet(urlString);
     HttpResponse response = httpClient.execute(request);
     return response.getEntity().getContent();
    }

}


James A Wilson
Thanks for this very nice piece of code.
Janusz
Brilliant piece of code.Congrats and ty
weakwire
Thanks for sharing the code!
Vinayak.B
+2  A: 

James,

Thanks for the code. However, you should use a java.lang.ref.SoftReference in order to avoid the app to grow too fat in memory and risking it being killed by the OS.

Bao-Long

Bao-Long Nguyen-Trong
this should be rephrased or posted as a comment to the original question
Janusz
+1 for "grow too fat in memory" :)
RueTheWhirled
+4  A: 

Thanks to James for the code, and Bao-Long for the suggestion of using SoftReference. I implemented the SoftReference changes on James' code. Unfortunately SoftReferences caused my images to be garbage collected too quickly. In my case it was fine without the SoftReference stuff, because my list size is limited and my images are small.

There's a discussion from a year ago regarding the SoftReferences on google groups: link to thread. As a solution to the too-early garbage collection, they suggest the possibility of manually setting the VM heap size using dalvik.system.VMRuntime.setMinimumHeapSize(), which is not very attractive to me.

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

public Drawable fetchDrawable(String urlString) {
    SoftReference<Drawable> drawableRef = drawableMap.get(urlString);
    if (drawableRef != null) {
        Drawable drawable = drawableRef.get();
        if (drawable != null)
            return drawable;
        // Reference has expired so remove the key from drawableMap
        drawableMap.remove(urlString);
    }

    if (Constants.LOGGING) Log.d(this.getClass().getSimpleName(), "image url:" + urlString);
    try {
        InputStream is = fetch(urlString);
        Drawable drawable = Drawable.createFromStream(is, "src");
        drawableRef = new SoftReference<Drawable>(drawable);
        drawableMap.put(urlString, drawableRef);
        if (Constants.LOGGING) Log.d(this.getClass().getSimpleName(), "got a thumbnail drawable: " + drawable.getBounds() + ", "
                + drawable.getIntrinsicHeight() + "," + drawable.getIntrinsicWidth() + ", "
                + drawable.getMinimumHeight() + "," + drawable.getMinimumWidth());
        return drawableRef.get();
    } catch (MalformedURLException e) {
        if (Constants.LOGGING) Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
        return null;
    } catch (IOException e) {
        if (Constants.LOGGING) Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
        return null;
    }
}

public void fetchDrawableOnThread(final String urlString, final ImageView imageView) {
    SoftReference<Drawable> drawableRef = drawableMap.get(urlString);
    if (drawableRef != null) {
        Drawable drawable = drawableRef.get();
        if (drawable != null) {
            imageView.setImageDrawable(drawableRef.get());
            return;
        }
        // Reference has expired so remove the key from drawableMap
        drawableMap.remove(urlString);
    }

    final Handler handler = new Handler() {
        @Override
        public void handleMessage(Message message) {
            imageView.setImageDrawable((Drawable) message.obj);
        }
    };

    Thread thread = new Thread() {
        @Override
        public void run() {
            //TODO : set imageView to a "pending" image
            Drawable drawable = fetchDrawable(urlString);
            Message message = handler.obtainMessage(1, drawable);
            handler.sendMessage(message);
        }
    };
    thread.start();
}
TalkLittle
A: 

well what method to use/call from those series of fetch()

mikedroid
+33  A: 

I made a simple demo of lazy list with images. May be helpful to somebody. It downloads images in the background thread. Images are being cached on SD card and in memory. I create just one thread and place download tasks into the queue. I think that's much more effective than multiple threads downloading and decoding images simultaneously. The cache implementation is very simple just enough for the demo. I decode images with inSampleSize to reduce memory consumption. I also try to handle recycled views correctly.

Source is available here http://open-pim.com/tmp/LazyList.zip

alt text

Fedor
This is really great one....Thanx Fedor
PM - Paresh Mayani
Very good! Thanks Fedor
DroidIn.net
This is a great piece of code! Exactly what I was looking for. Does require an SD card, but still the most efficient way I have found. Great job, and thanks!
Daniel
I'v tried sooo many ways and wasted way too many hours trying to to get this to work nicely...your implementation is just about perfect! Great piece of code indeed!
Kman
I'll have a look into it, but from just running your application, it looks awesome. Thank you Fedor!
Fabio Milheiro
I'm going to have a look at this code tomorrow. You've probably saved me some time working this out, as it's quite a regular pattern. Thanks!
Tim Almond
@Fedor, I have just posted a question about your project. If you or anyone else can help me on this one, I appreciate it. Here's the link:http://stackoverflow.com/questions/3791930/android-application-socketexception-permission-denied-no-such-file-or-directory
Fabio Milheiro
Thank you, I'm using a slightly modified version of your code almost everywhere in my project:http://code.google.com/p/vimeoid/source/browse/apk/src/com/fedorvlasov/lazylist/ImageLoader.java
shaman.sir
Very usefull.. Thanks for sharing
Vinayak.B
Has anyone looked at the code by Gilles Debunne as an alternative. The two big differences I see are 1) in memory caching vs SD card and 2) the use of AsyncTasks instead of a thread pool. http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html
Richard
A: 

I know it's been a long time since this thread started, but I was wondering if access to the drawable cache is thread safe in the above code? Isn't there a possible race condition if the ui thread is getting a value out of the cache while a spawned download thread is putting one in?

+1  A: 

Here is a very good tutorial from the Android Blog: http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html

Thomas Ahle
A: 

I have a small ( BIG ) problem with thous codes above. I have used some features from the two types of code and I have problem when some image from the "down" elements is queued for downloading, and the user scrolls to the "up" elements in the list. Then one image is displayed in wrong position in the "up" elements. I have included the image.tag (image.getTag()) to compare if the imageView is the right one.

Please help me.

Thanks for thous codes. You helped me a lot.

Daniel