在UI线程之外,多线程处理Bitmaps

多线程处理Bitmaps


    上一篇,我们讨论了:Android有效的处理Bitmap,减少内存 ,但是最好不要执行在主线程(UI线程),如果图片是本地的或者网络的又或者是其他地方的。图片加载的时间和许多因素有关(比如从网络或本地读取速度,图片的大小,CPU的能力),如果这些任务阻塞了UI线程,系统有可能会回收并关闭它(see Designing for Responsiveness for more information).
    这篇我们将讨论如何在UI线程之外后台使用异步任务(AsyncTask)添加Bitmap(本地或者网络图片或者其他资源的图片),并且叫你如何并发处理他们。

  使用异步任务

     异步任务提供了一种方便的方法来实现UI线程和后台线程的交互。我们可以派生一个类,并且重写他的方法。下面有个加载一张大图到ImageView的例子,使用上篇的降到的技术decodeSampledBitmapFromResource()(请看上篇的内容http://blog.csdn.net/liu8497/article/details/40786721):
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);
            }
        }
    }
}

    使用 WeakReference来保存ImageView为了内存能够很好的回收他们,但是不能保证异步任务结束的时候,该ImageView还存在,所以必须在
onPostExecute()检查是否为空。该ImageView可能为空,比如在异步任务退出之前,当Activity退出的时候或者其他配置改变的时候
  启动加载一个Bitmap只需要实例化一个任务并且执行它即可:
public void loadBitmap(int resId, ImageView imageView) {
    BitmapWorkerTask task = new BitmapWorkerTask(imageView);
    task.execute(resId);
}

并行处理

    常规组件,比如ListView和GridView在与异步任务结合的时候引入了其他问题。为了更好的使用内存,当用户滚动的时候就开始回收其他子View。如果每个子View都使用一个异步任务,我们不能保证当异步任务结束的时候,相关联的view没有被回收掉。我们也不能保证异步任务按照我们的顺序执行。
  有一篇博客 Multithreading for Performance,深入的讨论了并行处理的问题,并且提供了一种解决方法,其ImageView指向了最近的一个异步任务,并且可以在任务结束之后检验执行。运用相同的方法,上面提到的异步任务可以遵循使用类似的路线。
  创建一个专用的Drawable来存储指向该任务的资源。所以,我们使用BitmapDrawable,这样在任务结束的时候我们的image就展示在ImageView里面:
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();
    }
}
在执行 BitmapWorkerTask之前,我们可以给ImageView绑定一个AsyncDrawable。
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);
    }
}

   cancelPotentialWork这个方法检查是否有其他任务关联到该ImageView。如果是的话,我们使用函数cancel()取消掉关联的任务。很少情况下,新任务的数据与之前的匹配并且交互。下面是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;
}

一个辅助方法getBitmapWorkerTask(),用于检索与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;
}
  最后一步onPostExecute()在BitmapWorkerTask更新为了检验任务是否取消并且检验当前任务是否匹配该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 Bitma
c91
pWorkerTask bitmapWorkerTask =
                    getBitmapWorkerTask(imageView);
            if (this == bitmapWorkerTask && imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
}
现在,我们就能使用异步任务很好的在ListView和GridView里面或者其他组件里面更有效的回收资源。我们只需要执行loadBitmap 的方法就能放一张图片到ImageVIew里面。比如,在GridVIew里面,我们在Adapter的getView方法里面使用这种方法。



  


郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。