views:

238

answers:

3

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));
    }
}
A: 

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.

Jim Blackler
Yea, I thought it was something like that. How can I implement that inside the code above along side my downloadImage(result.getThumbnail(), myImageView); class???Thats all I need to run inside a thread really.Andy
Andy Barlow
Dispatching a new Thread for each image is a bad idea.
alex
No, I'd like to leave one thread open and ready to download images as the ArrayAdapter gets passed through. How can I make these changes in my code above?
Andy Barlow
Well you can have a Work Queue. These aren't that hard to write yourself, I've never had much satisfaction from the ones in the library. A good article is here http://www.ibm.com/developerworks/library/j-jtp0730.html#heading3 , also you can google 'java work queue'. Really what you are looking for some mechanism to cancel the downloads if the user exits the activity, and to avoid swamping the thread pool and http request list.
Jim Blackler
+2  A: 

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);
                        }
                    });
                }
            }
        });
    }
alex
This looks good, and I've tried to implement, but it doesn't work! Can anyone slip that into my code above for me??? Maybe i'm putting it all in the wrong place :(.
Andy Barlow
+1  A: 

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.

Yoni Samlan