Processing Bitmaps Off the UI Thread [在UI 线程之外处理Bitmap]

     Load Large Bitmaps Efficiently  中,讨论了BitmapFactory.decode*系列方法,如果图片来自硬盘或者网络(或者其他非内存的来源),是不应该放在在UI线程中执行的。这是因为加载这样的数据所需的时间是不确定的,它依赖于多个因素(从硬盘或网络的读取速度、图片的尺寸、CPU的速度等等)。如果这些任务里面任何一个阻塞了UI线程,系统会将你的应用标记为未响应(ANR),并且出现用户可以选择关闭应用的选项(更多信息,请参阅Designing for Responsiveness)

    这节课将介绍使用AsyncTask在后台线程处理Bitmap并向你展示如何处理并发问题。

Use an AsyncTask [使用AsyncTask]


    AsyncTask类提供了一种简单的方法,可以在后台线程处理一些事情,并将结果返回到UI线程。要使用它,需要创建一个它的子类,并且覆写它提供的方法。下面是一个使用AsyncTask和 decodeSampledBitmapFromResource()加载大图片到ImageView中的例子: 

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);
            }
        }
    }
}
         ImageView的WeakReference(弱引用)可以确保AsyncTask不会阻止ImageView和它的任何引用被垃圾回收器回收。不能保证在异步任务完成后ImageView依然存在,因此你必须在onPostExecute()方法中检查引用。ImageView可能已经不存在了,比如说,用户在任务完成前退出了当前Activity或者应用配置发生了变化(横屏)。

为了开始异步加载Bitmap,我们创建一个简单的异步任务并且执行它:

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

Handle Concurrency [处理并发]

   常见的View(视图)组件如ListView和GridView在与AsyncTask配合使用的时候引出了另外的问题,这个我们在上一节中提到过。为了提升内存效率,当用户滚动这些组件的时候进行子视图的回收(主要是回收不可见的视图)。如果每个子视图都触发了一个AsyncTask,无法保证在任务完成的时候,关联视图还没有被回收而被用来显示另一个子视图。此外,也无法保证异步任务结束的顺序与它们开始的顺序一致。

        Multithreading for Performance 这篇博文深入讨论了如何处理并发问题,并且给出了如何在任务结束的时候检测ImageView存储最近使用的AsyncTask引用的解决方案。使用相似的方法,可以遵循类似的模式来扩展前面的AsyncTask。

        创建一个专用的Drawable子类,用来保存一个可以回到当前工作任务的引用。在这种情况下,BitmapDrawable被用来作为图像占位符在任务结束时显示到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前,你需要创建一个AsyncDrawable并将之绑定到它的目标组件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);
    }
}
 在上面的代码示例中引用的cancelPotentialWork方法可以检查一个另外执行中的任务是否与ImageView有关联。如果有关联,它将通过调用canceel()方法试图取消之前的任务。在少数情况下,新的任务中的数据与现有的任务相匹配,因此不需要做什么。下面是calcelPotentialWork的具体实现:  
public static boolean cancelPotentialWork(int data, ImageView imageView) {
    final BitmapWorkerTask bitmapWorkerTask = 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 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;
}

最后一步是在BitmapWorkerTaskonPostExecute()方法中做更新操作,以便检测与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);
            }
        }
    }
}

    这里的方法不仅适用于ListView、GridView也适用于其他任何需要回收子视图的组件中。当你只需要为ImageView设置图片,调用loadBitmap就可以了。例如,在GridView中实现的方式是在Adapter的getView()方法中。

你可能感兴趣的:(Processing Bitmaps Off the UI Thread [在UI 线程之外处理Bitmap])