图片加载AsyncTask的并发问题

在列表控件中使用AsycnTask加载图片时,会带来并发问题。

如果每个子视图都触发一个AsyncTask,因为AsyncTask内部是一个线程池,并发触发时,不能确保每个子视图的AsyncTask都进入了队列,而且异步任务的完成顺序和启动顺序也不一定一致。

Multithreading For Performance这篇文章提供了一种方法。

主要方案如下:

  1. 通过定制一个BitmapDrawable,让ImageView储存当前AsyncTask的引用(弱引用)

    BitmapLoadTask,一个加载图片的AsyncTask,可以执行从网络、文件加载图片

    class AsyncDrawable extends BitmapDrawable {
    private final WeakReference<DownloadTask> downloadTaskWeakReference;
        
    AsyncDrawable(BitmapLoadTask downloadTask) {
        this.downloadTaskWeakReference = new WeakReference<>(downloadTask);
    }
    public DownloadTask getDownloadTask() {
        return downloadTaskWeakReference.get();
    }
    }
    
  2. ListView显示一个ImageView,并开始下载之前,判断是否有另一个AsyncTask已经与该ImageView绑定

    1. 如果存在一个Task,并且它的任务就是当前ImageView的任务,则不会新建一个AsyncTask去下载
    2. 如果不存在,或者存在的任务执行的下载不同于当前的任务,就取消当期的Task,然后新建一个。

    下载时判断:

    public void loadBitmap(String url, ImageView imageView) {
        if (shouldNewTaskToLoad(url, imageView)) {
            final BitmapLoadTask bitmapLoadTask = new BitmapLoadTask(imageView);
            final AsyncDrawable asyncDrawable = new AsyncDrawable(bitmapLoadTask);
            imageView.setImageDrawable(asyncDrawable);
            bitmapLoadTask.execute(url);
        }
    }
    

    java

    判断的逻辑:

    public static boolean shouldNewTaskToLoad(String url, ImageView imageView) {
        if (imageView != null) {
            AsyncDrawable asyncDrawable = (AsyncDrawable) imageView.getDrawable();
            if (asyncDrawable != null) {
                BitmapLoadTask bitmapLoadTask = asyncDrawable.getDownloadTask();
                if (bitmapLoadTask != null) {
                    //如果当前要下载的图片的地址与ImageView中储存的Task下载的地址不想等
                    if (url == null || (!url.equals(bitmapLoadTask.url))) {
                        bitmapLoadTask.cancel(true);
                    } else {
                        return false;
                    }
                }
            }
        }
        return true;
    }
    
  3. 使用:在加载图片时:使用setImageDrawable将AsyncTask与ImageView关联

    if (shouldNewTaskToLoad(url, imageView)) {
       final BitmapLoadTask bitmapLoadTask = new BitmapLoadTask(imageView);
       final AsyncDrawable asyncDrawable = new AsyncDrawable(bitmapLoadTask);
       imageView.setImageDrawable(asyncDrawable);
       bitmapLoadTask.execute(url);
        }