Android---在UI线程之外处理位图

本文译自:http://developer.android.com/training/displaying-bitmaps/process-bitmap.html

如果源图片来自磁盘或网络(或者其他任何内存以外地方),那么在“高效的加载大位图”一文中所讨论的BitmapFactory.decode*方法就不应该在主UI线程中执行。加载图片所需的时间是不可预知的,并且还要依赖各种因素(如磁盘或网络的读取速度、图片的尺寸、CPU的处理能力等)。如果这些因素中有一个阻塞了UI线程,那么系统把你的应用程序标记为非响应程序,并且用户可以选择关闭它。

本文讨论如何使用AsyncTask在后台线程中处理位图,以及如何处理并发问题。

使用AsyncTask

AsyncTask类为在后台线程中执行某些任务提供了比较容易的方法,并且它会把执行结果返回给UI线程。要使用它,就要创建一个子类,并重写相应的方法。以下是使用AsyncTask类和decodeSampleBitmapFromResource()方法把大位图加载到一个ImageView中的例子:

classBitmapWorkerTaskextendsAsyncTask<Integer,Void,Bitmap>{
   
private final WeakReference<ImageView> imageViewReference;
    private int data = 0;

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

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

    // Once complete, see ifImageView 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);
            }
        }
    }
}

ImageView对象使用WeakReference来确保AsyncTask不会妨碍ImageView对象,以及任何被放到垃圾回收站中的该对象的引用。因此在该任务执行完成时,并不保证ImageView对象还存在,因此你必须在onPostExecute()方法中检查该引用。例如,如果用户离开该Activity,或者在任务完成之前,相关的配置发生了变化,那么ImageView就可能不再存在。

要异步的加载位图,只需简单的创建一个新的任务并执行它:

publicvoidloadBitmap(int resId,ImageViewimageView){
   
BitmapWorkerTask task = new BitmapWorkerTask(imageView);
    task.execute(resId);
}

处理并发

通常当诸如ListViewGridView这样的View组件跟AsyncTask结合使用时,就会引入另外的问题。为了提高内存的使用效率,这些组件会在用户滚动时会回收子View。如果每个子View都触发一个AsyncTask任务,那么不能够保证AsyncTask任务被执行完成之前,相关的子View不被回收。此外,也不能够保证异步任务按照启动顺序来完成。

创建一个专用的Drawable子类来存储工作任务的引用。在本文中使用BitmapDrawable,以便在任务执行完成时,其对应的图片能够被显示在ImageView中。

staticclassAsyncDrawableextendsBitmapDrawable{
   
private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;

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

    public BitmapWorkerTaskgetBitmapWorkerTask() {
        return bitmapWorkerTaskReference.get();
    }
}

在执行BitmapWorkerTask之前,你要创建一个AsyncDrawable对象,并把它跟目标的ImageView对象绑定:

publicvoidloadBitmap(int resId,ImageViewimageView){
   
if (cancelPotentialWork(resId, imageView)) {
        final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
        final AsyncDrawableasyncDrawable =
                new AsyncDrawable(getResources(), mPlaceHolderBitmap,task);
        imageView.setImageDrawable(asyncDrawable);
        task.execute(resId);
    }
}

上例代码中的cancelPotentialWork方法会检查其他正在运行的任务是否已经跟该ImageView对象相关联。如果关联了,它会尝试调用cancel()方法来取消之前的任务。在少数场合,新任务的数据会跟既存的任务相匹配,并且不会再有其他的需求发生。以下是cancelPotentialWork方法的实现:

publicstaticbooleancancelPotentialWork(int data,ImageViewimageView){
   
final BitmapWorkerTaskbitmapWorkerTask =getBitmapWorkerTask(imageView);

    if (bitmapWorkerTask != null) {
        final int bitmapData = bitmapWorkerTask.data;
        if (bitmapData != data) {
            // Cancel previous task
            bitmapWorkerTask.cancel(true);
        } else {
            // The same work is already in progress
            return false;
        }
    }
    // No task associated withthe ImageView, or an existing task was cancelled
    return true;
}

一个辅助的方法:getBitmapWorkerTask()被用于获取与上述任务相关联的ImageView对象:

privatestaticBitmapWorkerTaskgetBitmapWorkerTask(ImageViewimageView){
   
if (imageView != null) {
       final Drawable drawable = imageView.getDrawable();
       if (drawable instanceof AsyncDrawable) {
           final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
           return asyncDrawable.getBitmapWorkerTask();
       }
    }
    return null;
}

最后是更新BitmapWorkerTask类的onPostExecute()方法,以便它能检查该任务是否被取消,以及当前任务是否被分配了一个匹配的ImageView对象:

classBitmapWorkerTaskextendsAsyncTask<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);
            }
        }
    }
}

现在,这种实现就可以适用于ListViewGridView组件,以及其他任何回收子View的组件。简单的调用loadBitmap方法,就可以把一个图片设置给ImageView对象。例如,在GridView中会在适配器支持的getView方法中实现这种模式。

 

示例代码:http://download.csdn.net/detail/fireofstar/4874551

你可能感兴趣的:(Android---在UI线程之外处理位图)