利用AsyncTask高效异步加载图片,适用于ListView 和GridView

Processing Bitmaps Off the UI Thread

This lesson teaches you to

  1. Use an AsyncTask
  2. Handle Concurrency

You should also read

Try it out

DisplayingBitmaps.zip

The BitmapFactory.decode* methods, discussed in the Load Large BitmapsEfficiently lesson, should not be executed on the main UI thread if the source data is read fromdisk or a network location (or really any source other than memory). The time this data takes toload is unpredictable and depends on a variety of factors (speed of reading from disk or network,size of image, power of CPU, etc.). If one of these tasks blocks the UI thread, the system flagsyour application as non-responsive and the user has the option of closing it (see Designing for Responsiveness formore information).

This lesson walks you through processing bitmaps in a background thread usingAsyncTask and shows you how to handle concurrency issues.

Use an AsyncTask

The AsyncTask class provides an easy way to execute some work in a backgroundthread and publish the results back on the UI thread. To use it, create a subclass and override theprovided methods. Here’s an example of loading a large image into an ImageView using AsyncTask and decodeSampledBitmapFromResource():

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    private final WeakReference<ImageView> imageViewReference;
    private int data = 0;

    public BitmapWorkerTask(ImageView imageView) {
        // Use a WeakReference to ensure the ImageView can be garbage collected
        imageViewReference = new WeakReference<ImageView>(imageView);
    }

    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        data = params[0];
        return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
    }

    // Once complete, see if ImageView is still around and set bitmap.
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (imageViewReference != null && bitmap != null) {
            final ImageView imageView = imageViewReference.get();
            if (imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
}

The WeakReference to the ImageView ensures that theAsyncTask does not prevent the ImageView and anything itreferences from being garbage collected. There’s no guarantee the ImageViewis still around when the task finishes, so you must also check the reference in onPostExecute(). The ImageViewmay no longer exist, if for example, the user navigates away from the activity or if aconfiguration change happens before the task finishes.

To start loading the bitmap asynchronously, simply create a new task and execute it:

public void loadBitmap(int resId, ImageView imageView) {
    BitmapWorkerTask task = new BitmapWorkerTask(imageView);
    task.execute(resId);
}

Handle Concurrency

Common view components such as ListView and GridView introduce another issue when used in conjunction with the AsyncTask as demonstrated in the previous section. In order to be efficient with memory,these components recycle child views as the user scrolls. If each child view triggers an AsyncTask, there is no guarantee that when it completes, the associated view has notalready been recycled for use in another child view. Furthermore, there is no guarantee that theorder in which asynchronous tasks are started is the order that they complete.

The blog post Multithreadingfor Performance further discusses dealing with concurrency, and offers a solution where theImageView stores a reference to the most recent AsyncTaskwhich can later be checked when the task completes. Using a similar method, the AsyncTask from the previous section can be extended to follow a similar pattern.

Create a dedicated Drawable subclass to store a referenceback to the worker task. In this case, a BitmapDrawable is used sothat a placeholder image can be displayed in the ImageView while the taskcompletes:

static class AsyncDrawable extends BitmapDrawable {
    private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;

    public AsyncDrawable(Resources res, Bitmap bitmap,
            BitmapWorkerTask bitmapWorkerTask) {
        super(res, bitmap);
        bitmapWorkerTaskReference =
            new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
    }

    public BitmapWorkerTask getBitmapWorkerTask() {
        return bitmapWorkerTaskReference.get();
    }
}

Before executing the BitmapWorkerTask, you create an AsyncDrawable and bind it to the target ImageView:

public void loadBitmap(int resId, ImageView imageView) {
    if (cancelPotentialWork(resId, imageView)) {
        final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
        final AsyncDrawable asyncDrawable =
                new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
        imageView.setImageDrawable(asyncDrawable);
        task.execute(resId);
    }
}

The cancelPotentialWork method referenced in the code sample above checks if anotherrunning task is already associated with the ImageView. If so, it attempts tocancel the previous task by calling cancel(). In a small numberof cases, the new task data matches the existing task and nothing further needs to happen. Here isthe implementation of cancelPotentialWork:

public static boolean cancelPotentialWork(int data, ImageView imageView) {
    final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);

    if (bitmapWorkerTask != null) {
        final int bitmapData = bitmapWorkerTask.data;
        // If bitmapData is not yet set or it differs from the new data
        if (bitmapData == 0 || bitmapData != data) {
            // Cancel previous task
            bitmapWorkerTask.cancel(true);
        } else {
            // The same work is already in progress
            return false;
        }
    }
    // No task associated with the ImageView, or an existing task was cancelled
    return true;
}

A helper method, getBitmapWorkerTask(), is used above to retrieve the task associatedwith a particular ImageView:

private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
   if (imageView != null) {
       final Drawable drawable = imageView.getDrawable();
       if (drawable instanceof AsyncDrawable) {
           final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
           return asyncDrawable.getBitmapWorkerTask();
       }
    }
    return null;
}

The last step is updating onPostExecute() in BitmapWorkerTask so that it checks if the task is cancelled and if the current task matches theone associated with the ImageView:

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    ...

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (isCancelled()) {
            bitmap = null;
        }

        if (imageViewReference != null && bitmap != null) {
            final ImageView imageView = imageViewReference.get();
            final BitmapWorkerTask bitmapWorkerTask =
                    getBitmapWorkerTask(imageView);
            if (this == bitmapWorkerTask && imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
}

This implementation is now suitable for use in ListView and GridView components as well as any other components that recycle their childviews. Simply call loadBitmap where you normally set an image to your ImageView. For example, in a GridView implementation thiswould be in the getView() method of the backing adapter.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值