6

Possible Duplicate:
Android - How do I do a lazy load of images in ListView

I have been trying to figure this out for a few days but I can't seem to wrap my mind around the process. My app access a number of images from a server. As of now, it is setup to load 1 image at a time and display it. When the user hits the next button, the next image is then loaded and displayed. But the loading time is a little too long. What can be done to improve the load time of the next image?

I have been playing around with threads and AsyncTask. My idea was to keep the previous and next images in memory too. When the user hits next, I do the following:

prevImage = currentImage;
currentImage = nextImage;
nextImage = getBitmapfromURL(urlPath);

And the nextImage is actually executed in the AsyncTask or Thread. My problem with this is if the user hits the next button before that thread is completed (which simply shows a blank image). So I'm not sure if that's the way to go. Is there another way to improve the load time of these images?

Nimantha
  • 6,405
  • 6
  • 28
  • 69
Brian
  • 819
  • 4
  • 20
  • 35
  • Refer this question http://stackoverflow.com/questions/541966/android-how-do-i-do-a-lazy-load-of-images-in-listview , here you can plenty of ideas, people had said different paths to reach this destination. – Sankar Ganesh PMP Feb 21 '11 at 17:56
  • Thanks for the reply. That code looks exactly like what Rohit posted. But I can't figure out how to convert that example that is designed for a List view into my design, that only shows 1 image at a time. I added the ImageLoader and Utils classes. Then I added imageLoader=new ImageLoader(getApplicationContext()); imageLoader.DisplayImage(firstImages[0], this, imgView); in my classes onCreate method to load the first image. but my image isn't displayed. Any thoughts? – Brian Feb 21 '11 at 19:26
  • Figured out the problem. You have to use imgView.setTag(url); Without setting the tag, it doesn't update the imageView after downloading. Thanks for pointing me to the thread. And thanks Rohit. I just needed to see how he was using the ImageLoader class. – Brian Feb 21 '11 at 21:16
  • I very glad to see, that you had figured out your problem, nice Friend – Sankar Ganesh PMP Feb 22 '11 at 07:14
  • Well I guess I spoke too soon. I did get that to work for a little bit. But I am getting an Out of Memory error after I browse through a few pictures. At the top of Fedor's code, he comments that you should probably change it to use SoftReferences. I played around with it and got it to compile, but I can't get any images to load now. I have never used Soft References before so I'm sure I'm doing it wrong. How would you go about implementing SoftReferences on his code? That should eliminate the OOM error, right? Thanks. – Brian Feb 22 '11 at 18:53

1 Answers1

1

Here is my class for loading image from server.

package test;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.net.URL;
import java.util.HashMap;
import java.util.Stack;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.widget.ImageView;

import com.com.app.R;

public class ImageLoader {

    // the simplest in-memory cache implementation. This should be replaced with
    // something like SoftReference or BitmapOptions.inPurgeable(since 1.6)
    private HashMap<String, Bitmap> cache = new HashMap<String, Bitmap>();

    private File cacheDir;

    public ImageLoader(Context context) {
        // Make the background thead low priority. This way it will not affect
        // the UI performance
        photoLoaderThread.setPriority(Thread.NORM_PRIORITY - 1);

        // Find the dir to save cached images
        if (android.os.Environment.getExternalStorageState().equals(
                android.os.Environment.MEDIA_MOUNTED))
            cacheDir = new File(
                    android.os.Environment.getExternalStorageDirectory(),
                    "LazyList");
        else
            cacheDir = context.getCacheDir();
        if (!cacheDir.exists())
            cacheDir.mkdirs();
    }

    final int stub_id = R.drawable.no_image;

    public void DisplayImage(String url, Activity activity, ImageView imageView) {
        if (cache.containsKey(url))
            imageView.setImageBitmap(cache.get(url));
        else {
            queuePhoto(url, activity, imageView);
            imageView.setImageResource(stub_id);
        }
    }

    private void queuePhoto(String url, Activity activity, ImageView imageView) {
        // This ImageView may be used for other images before. So there may be
        // some old tasks in the queue. We need to discard them.
        photosQueue.Clean(imageView);
        PhotoToLoad p = new PhotoToLoad(url, imageView);
        synchronized (photosQueue.photosToLoad) {
            photosQueue.photosToLoad.push(p);
            photosQueue.photosToLoad.notifyAll();
        }

        // start thread if it's not started yet
        if (photoLoaderThread.getState() == Thread.State.NEW)
            photoLoaderThread.start();
    }

    private Bitmap getBitmap(String url) {
        // // I identify images by hashcode. Not a perfect solution, good for
        // the
        // // demo.
        // String filename = String.valueOf(url.hashCode());
        // File f = new File(cacheDir, filename);
        //
        // // from SD cache
        // Bitmap b = decodeFile(f);
        // if (b != null)
        // return b;

        // from web
        try {
            return new BitmapDrawable(new URL(url).openStream()).getBitmap();
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }

    // decodes image and scales it to reduce memory consumption
    private Bitmap decodeFile(File f) {
        try {
            // decode image size
            BitmapFactory.Options o = new BitmapFactory.Options();
            o.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(new FileInputStream(f), null, o);

            // Find the correct scale value. It should be the power of 2.
            final int REQUIRED_SIZE = 70;
            int width_tmp = o.outWidth, height_tmp = o.outHeight;
            int scale = 1;
            while (true) {
                if (width_tmp / 2 < REQUIRED_SIZE
                        || height_tmp / 2 < REQUIRED_SIZE)
                    break;
                width_tmp /= 2;
                height_tmp /= 2;
                scale++;
            }

            // decode with inSampleSize
            BitmapFactory.Options o2 = new BitmapFactory.Options();
            o2.inSampleSize = scale;
            return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
        } catch (FileNotFoundException e) {

        }

        return null;
    }

    // Task for the queue
    private class PhotoToLoad {
        public String url;
        public ImageView imageView;

        public PhotoToLoad(String u, ImageView i) {
            url = u;
            imageView = i;
        }
    }

    PhotosQueue photosQueue = new PhotosQueue();

    public void stopThread() {
        photoLoaderThread.interrupt();
    }

    // stores list of photos to download
    class PhotosQueue {
        private Stack<PhotoToLoad> photosToLoad = new Stack<PhotoToLoad>();

        // removes all instances of this ImageView
        public void Clean(ImageView image) {
            for (int j = 0; j < photosToLoad.size();) {
                if (photosToLoad.get(j).imageView == image)
                    photosToLoad.remove(j);
                else
                    ++j;
            }
        }
    }

    class PhotosLoader extends Thread {
        public void run() {
            try {
                while (true) {
                    // thread waits until there are any images to load in the
                    // queue
                    if (photosQueue.photosToLoad.size() == 0)
                        synchronized (photosQueue.photosToLoad) {
                            photosQueue.photosToLoad.wait();
                        }
                    if (photosQueue.photosToLoad.size() != 0) {
                        PhotoToLoad photoToLoad;
                        synchronized (photosQueue.photosToLoad) {
                            photoToLoad = photosQueue.photosToLoad.pop();
                        }
                        Bitmap bmp = getBitmap(photoToLoad.url);
                        cache.put(photoToLoad.url, bmp);

                        if (photoToLoad.url == null) {
                            photoToLoad.url = "http://192.168.0.2/phpdemoprojects/cardsonmobile/uploads/cards/0009.png";
                        } else {

                            if (((String) photoToLoad.imageView.getTag())
                                    .equals(photoToLoad.url)) {
                                BitmapDisplayer bd = new BitmapDisplayer(bmp,
                                        photoToLoad.imageView);
                                Activity a = (Activity) photoToLoad.imageView
                                        .getContext();
                                a.runOnUiThread(bd);
                            }
                        }
                    }
                    if (Thread.interrupted())
                        break;
                }
            } catch (InterruptedException e) {
                // allow thread to exit
            }
        }
    }

    PhotosLoader photoLoaderThread = new PhotosLoader();

    // Used to display bitmap in the UI thread
    class BitmapDisplayer implements Runnable {
        Bitmap bitmap;
        ImageView imageView;

        public BitmapDisplayer(Bitmap b, ImageView i) {
            bitmap = b;
            imageView = i;
        }

        public void run() {
            if (bitmap != null)
                imageView.setImageBitmap(bitmap);
            else
                imageView.setImageResource(stub_id);
        }
    }

    public void clearCache() {
        // clear memory cache
        cache.clear();

        // clear SD cache
        File[] files = cacheDir.listFiles();
        for (File f : files)
            f.delete();
    }

}
Nimantha
  • 6,405
  • 6
  • 28
  • 69
Rohit Mandiwal
  • 10,258
  • 5
  • 70
  • 83
  • So basically, you are storing all the images to the SD card and then accessing them when needed, right? So for me to do it this way, I would have to make the user wait for the images to be downloaded before I let them hit the next/previous buttons. I don't think I want that because it could take a couple minutes to load by doing this. Thanks for your reply. – Brian Feb 21 '11 at 17:04
  • No, the user has not to wait to download all images.. image URLs are fed to this and it will keep on downloading in background and portrait them instantly when needed. I used this in association of my custom list adapter – Rohit Mandiwal Feb 21 '11 at 17:13
  • I understand your use of it. By for what I'm trying to do, I do not want the images to load until a button is clicked to view the images. For example, I have a screen with a Spinner item with different categories. Then when the user clicks a button, a new screen appears that shows a number of pictures based on which category was selected in the spinner. So I won't know which images to load until it is time to view the images, which means I would have to wait for them to load to view the next images. If I am still not understanding you correctly, let me know. Thanks again. – Brian Feb 21 '11 at 18:34