Bitmap处理 之三在子线程处理位图

有效地展示大位图,不应该在主UI线程上执行如果源数据从磁盘读取或网络位置(或真正内存以外的任何来源)。加载数据需要的时间是不可预测的,取决于多种因素(阅读从磁盘或网络,速度大小的图像,CPU,等等)。如果其中一个任务阻塞UI线程,系统应用程序没有响应,用户选择关闭。

So为了用户体验,通过在后台线程处理位图,这里使用AsyncTask,并且展示如何处理并发问题。

使用异步任务(AsyncTask)

AsyncTask类提供了简单方法后台执行任务,UI线程返回结果。如何使用呢,创建一个子类继承它,下面提供一个示例,如何加载大图片:

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    private final WeakReference<ImageView> imageViewReference;
    private int data = 0;

    public BitmapWorkerTask(ImageView imageView) {
        // 使用若引用,确保可以被回收
        imageViewReference = new WeakReference<ImageView>(imageView);
    }

    // 后台解析
    @Override
    protected Bitmap doInBackground(Integer... params) {
        data = params[0];
        //上篇说到请参考http://blog.csdn.net/hello_12413/article/details/48261503
        return decodeSampledBitmapFromResource(getResources(), data, 100, 100));

    }

    //完成加载
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (imageViewReference != null && bitmap != null) {
            final ImageView imageView = imageViewReference.get();
            if (imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
}

上面的弱引用是为了确保能被回收。无法保证当任务完成的时候ImageView还在,所以在onPostExecute需要核对。
开始异步加载位图,简单地创建一个新的任务并执行:

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

处理并发

普通视图比如ListView,GridView引入了另一个问题,使用时结合 AsyncTask。为了有效地使用内存资源,这些组件回收item当用户滑动的时候。如果每个item触发一个 AsyncTask,没法保证当任务完成的时候这个item没有被回收用于其他item。也没法保证任务启动的顺序和完成的顺序一至。
首先写一个子类继承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();
    }
}

首先在执行任务之前,要新建一个AsyncDrawble对象,并绑定到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);
    }
}

然后因为列表可能上下翻动,所以需要判断任务是否已经存在,如果是那么会调用当前的cancel(),方法取消

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()更新UI,检查任务是否取消,是否与当前任务匹配,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 这样的控件,可以在Adapter的getView方法里显示图片。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值