高效显示Bitmap之UI线程外处理Bitmap

来源:互联网 发布:淘宝图标 编辑:程序博客网 时间:2024/05/16 01:05

如果数据源从硬盘读取或者网络, BitmapFactory.decode*方法不应该在main线程中执行。加载这些数据的时间是不可预知的,受限于多种因素(硬盘读取速度,图片大小,CPU等等)。如果任一因素锁住了UI线程,系统会标记你的APP无响应,用户会有选择关闭其操作(see Designing for Responsiveness for more information)。

这节课教你通过AsyncTask 在后台线程加载bitmap以及怎么解决并发性问题。

Use an AsyncTask

AsyncTask提供一个简单的方式在后台处理,之后把结果提交到前台。如何使用,创建一个子类重写方法。下面是一个例子:

AsyncTask decodeSampledBitmapFromResource()方法加载一张大图到ImageView

class BitmapWorkerTask extends AsyncTask {    private final WeakReference imageViewReference;    private int data = 0;    public BitmapWorkerTask(ImageView imageView) {        // Use a WeakReference to ensure the ImageView can be garbage collected        imageViewReference = new WeakReference(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


在之前, 在使用AsyncTask时, 通常 ListView 和 GridView 视图组件会引发另一个问题。为了有效的利用内存,在用户滚动视图时,这些组件会重复利用子布局。如果每个子视图触发一个AsyncTask,没法确保它是否完成,与之绑定的view还没来的及回收利用。此外,无法确保异步任务的完成的次序跟起动时一致。

专门创建一个 Drawable 子类来存储异步任务的引用。这样,当异步任务完成时,BitmapDrawable会被做为占位符图像显示在ImageView.

static class AsyncDrawable extends BitmapDrawable {    private final WeakReference bitmapWorkerTaskReference;    public AsyncDrawable(Resources res, Bitmap bitmap,            BitmapWorkerTask bitmapWorkerTask) {        super(res, bitmap);        bitmapWorkerTaskReference =            new WeakReference(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方法检查是否另一个运行着的线程已经与这个IamgeView绑定。如果是, 通过 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 != 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;}
BitmapWorkerTask中最后一步是修改下 onPostExecute(),这样检查线程是否被关闭了、当前的异步任务与当前的ImageView是否匹配。

class BitmapWorkerTask extends AsyncTask {    ...    @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以及其他重复利用子View的组件中,用以上方法都是可行的。在ImageView设置图片的地方简单的调用 loadBitmap即可。例如,在GridView组件中的getView()中调用。




0 0
原创粉丝点击