Question

I have the following code running on my Android device. It works great and displays my list items wonderfully. It's also clever in the fact it only downloads the data when it's needed by the ArrayAdapter. However, whilst the download of the thumbnail is occurring, the entire list stalls and you cannot scroll until it's finished downloading. Is there any way of threading this so it'll still scroll happily, maybe show a place holder for the downloading image, finish the download, and then show?

I think I just need to put my downloadImage class into it's own thread so its executed separately from the UI. But how to add this into my code is the mystery!

Any help with this would be really appreciated.

private class CatalogAdapter extends ArrayAdapter<SingleQueueResult> {

    private ArrayList<SingleQueueResult> items;

    //Must research what this actually does!
    public CatalogAdapter(Context context, int textViewResourceId, ArrayList<SingleQueueResult> items) {
        super(context, textViewResourceId, items);
        this.items = items;

    }

    /** This overrides the getview of the ArrayAdapter. It should send back our new custom rows for the list */
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        View v = convertView;
        if (v == null) {
            LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            v = vi.inflate(R.layout.mylists_rows, null);

        }
        final SingleQueueResult result = items.get(position);
        // Sets the text inside the rows as they are scrolled by!
        if (result != null) {

            TextView title = (TextView)v.findViewById(R.id.mylist_title);
            TextView format = (TextView)v.findViewById(R.id.mylist_format);
            title.setText(result.getTitle());
            format.setText(result.getThumbnail());

            // Download Images
            ImageView myImageView = (ImageView)v.findViewById(R.id.mylist_thumbnail);
            downloadImage(result.getThumbnail(), myImageView);

        }

        return v;
    }
}

// This should run in a seperate thread
public void downloadImage(String imageUrl, ImageView myImageView) {

    try {
        url = new URL(imageUrl);
        URLConnection conn = url.openConnection();
        conn.connect();
        InputStream is = conn.getInputStream();
        BufferedInputStream bis = new BufferedInputStream(is);
        Bitmap bm = BitmapFactory.decodeStream(bis);
        bis.close();
        is.close();
        myImageView.setImageBitmap(bm);
    } catch (IOException e) {
        /* Reset to Default image on any error. */
        //this.myImageView.setImageDrawable(getResources().getDrawable(R.drawable.default));
    }
}
Was it helpful?

Solution

Here's a simplified version of what I'm using in my apps:

// this lives in Application subclass and is reused
public static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor();

// the method itself

    public void attachImage(final String fileUrl, final ImageView view) {
        EXECUTOR.execute(new Runnable() {

            @Override
            public void run() {
                final Drawable image = methodThatGetsTheImage(fileUrl);
                if (image != null) {
                    view.post(new Runnable() {

                        @Override
                        public void run() {
                            view.setImageDrawable(drawable);
                        }
                    });
                }
            }
        });
    }

OTHER TIPS

You can also check out cwac-thumbnail for a drop-in solution (though it has a bit more overhead than the simpler solutions above). I've used it with great success pretty much anywhere I have a web image to download; you basically set up some global caching options for your app, then just wrap your ListAdapter in a ThumbnailAdapter with a list of android:ids of the imageviews in your list, and call setTag(imageUrl) on the imageviews in your adapter's getView method, and it handles the rest.

Yes. You should avoid running on the UI Thread as much as possible. When you're in getView() you are on the UI thread. To break out do this:

new Thread(new Runnable(){

  public void run() {
    // your code here

  }}).start();

Then when you need to modify a view in some way go back onto the UI thread like this:

runOnUiThread(new Runnable(){

  public void run() {
    // your view-modifying code here

  }});

.. or use the View.post() mechanism.

In your example what will happen is that the item will be shown on the screen before the image is available for display. I suggest using a 'loading' spinner or some other placeholder to indicate to the user what is happening.

You can in practice nest like this several times, although you will reach a point where a simplified design is in order. Anonymous classes can be inefficient memory-wise and firing off a lot of threads without a cancel mechanism can have its own drawbacks.

You may experience some difficulties as a side effect of modifying views after getView() has exited. Some views may need invalidate() called to have their changes take effect. Also beware if you are recycling views in your getView(), you should ensure that by the time your view-altering code is executed that the view is still being used for the content you are applying. setTag()/getTag() is useful here to store some ID of the data that you can later check to make sure your View hasn't been recycled.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top