1

I read somewhere (and have observed) that starting threads is slow. I always assumed that AsyncTask created and reused a single thread because it required being started inside the UI thread.

The following (anonymized) code is called from a ListAdapter's getView method to load images asynchronously. It works well until the user moves the list quickly, and then it becomes "janky".

final File imageFile = new File(getCacheDir().getPath() + "/img/" + p.image);
image.setVisibility(View.GONE);
view.findViewById(R.id.imageLoading).setVisibility(View.VISIBLE);
(new AsyncTask<Void, Void, Bitmap>() {
    @Override
    protected Bitmap doInBackground(Void... params) {
        try {
            Bitmap image;
            if (!imageFile.exists() || imageFile.length() == 0) {
                image = BitmapFactory.decodeStream(new URL(
                        "http://example.com/images/"
                                + p.image).openStream());
                image.compress(Bitmap.CompressFormat.JPEG, 85,
                        new FileOutputStream(imageFile));
                image.recycle();
            }
            image = BitmapFactory.decodeFile(imageFile.getPath(),
                bitmapOptions);
            return image;
        } catch (MalformedURLException ex) {
            // TODO Auto-generated catch block
            ex.printStackTrace();
            return null;
        } catch (IOException ex) {
            // TODO Auto-generated catch block
            ex.printStackTrace();
            return null;
        }
    }

    @Override
    protected void onPostExecute(Bitmap image) {
        if (view.getTag() != p) // The view was recycled.
            return;
            view.findViewById(R.id.imageLoading).setVisibility(
                View.GONE);
        view.findViewById(R.id.image)
                .setVisibility(View.VISIBLE);
        ((ImageView) view.findViewById(R.id.image))
                .setImageBitmap(image);
    }
}).execute();

I'm thinking that a queue-based method would work better, but I'm wondering if there is one or if I should attempt to create my own implementation.

Ben L.
  • 1,735
  • 1
  • 14
  • 19
  • 1
    You should optimize the code so that when the listview is in fling scroll mode should not issue compute threads. – Pentium10 Jun 12 '10 at 18:47
  • "I always assumed that AsyncTask created and reused a single thread because it required being started inside the UI thread." `AsyncTask` uses a thread pool of 10 threads, as of Android 1.6 or so. – CommonsWare Jun 12 '10 at 20:33

2 Answers2

1

The key is explained in the example List13.java

Basically you have to track the scroll state of your listView and notify the adapter when it is ready to do something of slow with the just visibile items.

Also note that saving images to the disk is a very slow process. Using a memory-based cache stategy would improve a lot your perfomance's application.

Francesco Laurita
  • 23,434
  • 8
  • 55
  • 63
  • Saving the images to disk only happens once per image, and these images get reused throughout the application. I tried memory-based caching, but the VM doesn't get enough memory to hold everything I need it to for that to work. – Ben L. Jun 12 '10 at 19:34
  • Are the images size so big? Did u try to wrap the Bitmap object inside a SoftReference? – Francesco Laurita Jun 12 '10 at 20:55
  • Come to think of it, with a combination of inInputShareable, inPurgeable, inPreferredConfig, and inSampleSize, I can store the images in a Hashtable without needing to worry about memory - they'll just reload themselves when they are reused. Thanks! – Ben L. Jun 13 '10 at 15:34
1
  1. I can see you decode image and compress it back to disk, after that you decode it again. Not very effective I think. You could just save the stream from network to disk, after that decompress it. That would be just one decompress instead of 3 compress/decompress. Will save you a lot of CPU processing time.

  2. I think AsyncTask creates several threads for several images. So several images are being compressed/decompressed at the same time, several threads fight for CPU time, not very good. As far as I know AsyncTask uses thread pool, so it doesn't start new thread for each image. But anyway several threads at the same time is not so good. I agree a queue would be much more effective. The implementation is not so hard to create it yourself. I use my own queue implementation and I'm quite happy with it.

  3. If you have your own thread I think it would be possible to give it a lower priority. That will make the UI more responsive.

  4. You certainly need some kind of in-memory cache, otherwise UI can't be fast enough. Decompress is slow. You can store not all images but only most used ones. You may use SoftReference for cache implementation. You may use inSampleSize option to make your bitmaps smaller and occupy less memory Strange out of memory issue while loading an image to a Bitmap object.

I made a complete example of LazyList and posted the source, may also be helpful Lazy load of images in ListView.

Community
  • 1
  • 1
Fedor
  • 43,261
  • 10
  • 79
  • 89
  • I decode it again so I can use inInputShareable/inPurgeable to regain memory automatically. If I just used the network copy, it would need to re-download the image to reuse it. – Ben L. Jun 13 '10 at 15:35
  • Yes I think inPurgeable is OK. It retains bitmap cached in memory. When you're low on memory bitmap will be removed and parsed again on demand. Actually I do absolutely the same with SoftReferences. – Fedor Jun 14 '10 at 13:21