2

I've taken a piece out of some of the major caching/image loading methods people have made and came up with something that uses the browsers cache and loads the images very fast.

My problem is that sometimes the images are not correct(duplicate of another image) and when I scroll down the list of imageViews the images flicker and change as scrolling. Is this something I have to live with?

Yes I create a new thread for each imageView. I tried one thread in a queue and it took too long to load. Even though they are all separate threads it should not get the imageView's loaded with the wrong drawables.

Here is my code.

public void fetchDrawableOnThread(final String urlString, final ImageView imageView) {

    if (imageView != null && urlString != null && urlString.length() > 0)
    {

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

        Thread thread = new Thread() {
            @Override
            public void run() {
                if (imageView != null && urlString != null && !urlString.equals(""))
                {
                    URL url;
                    try {
                        InputStream is = fetch(urlString);

                        if (is != null) {
                            Drawable drawable = Drawable.createFromStream((InputStream)is, "src");
                            Message message = handler.obtainMessage(1, drawable);
                            handler.sendMessage(message);
                        } 
                    }
                    catch (MalformedURLException e) {
                        Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
                    } catch (IOException e) {
                        Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
                    }
                }

            }
        };
        thread.start();
    }

}

private InputStream fetch(String urlString) throws MalformedURLException, IOException {
    if (urlString != null)
    {
        URL url = new URL(urlString);

        URLConnection connection = url.openConnection();
        connection.setUseCaches(true);
        Object response = connection.getContent();
        if (response instanceof InputStream) {
            return (InputStream) response;
        }
    }
    return null;
}

*EDIT***

After all the comments here is my code: I'm currently getting a 05-19 00:16:49.535: ERROR/AndroidRuntime(12213): java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@4070dab0 when I try to recycle.

Also, when I didnt have the recycle everything works except I see the images still flickering. And even sometimes the wrong image is displayed.

public void fetchDrawableOnThread(final String urlString, final ImageView imageView) {

    if (imageView != null && urlString != null && urlString.length() > 0)
    {
        imageView.setTag(urlString);

        final Handler handler = new Handler() {
            @Override
            public void handleMessage(Message message) {
                Drawable d = imageView.getDrawable();
                if(d != null){
                     ((BitmapDrawable)imageView.getDrawable()).getBitmap().recycle();
                     imageView.setImageBitmap(null);
                }

                imageView.setImageDrawable((Drawable) message.obj);
            }
        };

        Thread thread = new Thread() {
            @Override
            public void run() {
                if (imageView != null && urlString != null && !urlString.equals(""))
                {
                    URL url;
                    try {
                        if (imageView.getTag().equals(urlString))
                        {
                            InputStream is = fetch(urlString);

                            if (is != null) {
                                Drawable drawable = Drawable.createFromStream((InputStream)is, "src");
                                Message message = handler.obtainMessage(1, drawable);
                                handler.sendMessage(message);
                            } 
                        }
                    }
                    catch (MalformedURLException e) {
                        Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
                    } catch (IOException e) {
                        Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
                    }
                }

            }
        };
        thread.start();
    }

}

private InputStream fetch(String urlString) throws MalformedURLException, IOException {
    if (urlString != null)
    {
        URL url = new URL(urlString);

        URLConnection connection = url.openConnection();
        connection.setUseCaches(true);
        Object response = connection.getContent();
        if (response instanceof InputStream) {
            return (InputStream) response;
        }
    }
    return null;
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
kgibbon
  • 726
  • 1
  • 15
  • 37

2 Answers2

3

If you are using a ListView you should realize that the ListView reuses ImageView instances. If your loading takes too long and the specific ImageView that it was tied to has been reused for another Image then your implementation will overwrite the correct image with the old image. You need to:

  1. Modify your fetchDrawableOnThread method to mark the ImageView with the URL to be fetched: typically you would use setTag and save the url.
  2. Check in the run method after the InputStream is fetched to make sure that the getTag value in the ImageView matches the url you just fetched. If they don't match then the ListView has reused this ImageView instance and you should not send the Message to display the image.

You should also probably be calling recycle somewhere to clean up the Bitmap memory you're consuming before you set a new Drawable, but that's a longer story.

EDIT: the recycle story is actually simpler than I remembered (I looked up some old code). Basically, before you update the Drawable for an ImageView, check if it already has a Drawable set and if it does call recycle on it. So something like this:

ImageView iv = getImageView(); // fetch the image view somehow before you set it;
Drawable d = iv.getDrawable();
if(d != null){
     ((BitmapDrawable)iv.getDrawable()).getBitmap().recycle();
     iv.setImageBitmap(null);
}

This will reclaim the memory used by the previous Drawable, and should help with memory exhaustion issues.

Femi
  • 64,273
  • 8
  • 118
  • 148
  • Take a look at http://evancharlton.com/thoughts/lazy-loading-images-in-a-listview/: its got a longer writeup with some example code. You might also want to look at http://stackoverflow.com/questions/541966/android-how-do-i-do-a-lazy-load-of-images-in-listview – Femi May 17 '11 at 17:26
  • 1
    why doesnt anyone use setUseCaches(true) instead of saving to sd card? – kgibbon May 17 '11 at 20:29
  • As far as I can tell (someone correct me if I'm wrong) that function (`setUseCaches`) doesn't actually do any client-side caching, it just sets Cache-Control headers. You still need to implement a client-side cache. I"m guessing HTTP components provides just such an implementation somewhere in the many many classes it provides, but I'm yet to see anyone use them. Might be worth some thought, though. – Femi May 17 '11 at 20:53
  • what are the options for caching if the phone doesnt have external storage? – kgibbon May 17 '11 at 21:51
  • Ah, I'm assuming you can cache to the built-in memory, up to some limit. There is a `Context.getCacheDir()` method that will return a cache directory regardless of whether or not you have external storage. I'd be careful before you fill up the internal memory with images, but its there for you. – Femi May 17 '11 at 21:55
  • I put the recycle in the handler and it does not work. 05-19 00:10:38.539: ERROR/AndroidRuntime(12134): java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@406e8658 – kgibbon May 19 '11 at 04:14
  • public void handleMessage(Message message) { Drawable d = imageView.getDrawable(); if(d != null){ ((BitmapDrawable)imageView.getDrawable()).getBitmap().recycle(); imageView.setImageBitmap(null); } imageView.setImageDrawable((Drawable) message.obj); } – kgibbon May 19 '11 at 04:14
  • Yeah this doesn't work for me either. I get the same exception, trying to use a recycled bitmap even though I supply a new one after recycling the old one. – Kevin Parker Feb 24 '12 at 02:12
  • Odd. Try calling `setImageBitmap` to null before calling `recycle`. – Femi Feb 24 '12 at 17:36
1

A quick and easy solution that builds on top of the async loading and caching capabilities of a web view:

String htmlSnippet = "<html><head/><body>" +
                     "<img height=\"50px\" width=\"50px\" src=\"" + item.getImageUrl() + "\" />" +
                     "</body></html>";
imageWebview.loadData( htmlSnippet, "text/html", "utf-8" );
mgrol
  • 11
  • 1