位图管理、图片下载缓存、管理图片内存 (三) 在非UI线程中处理位图

        BitmapFactory.decode*等解码方法不应在主线程中执行,假如资源数据是从硬盘或者网络地址中读取的话(或者说除内存以外的其他任意位置)。这些数据可能花费的时间是不可预知的,依赖于一系列的因素(包括硬盘或者网络的读取速度,图片尺寸,CPU处理能力等)。如果其中某个因素阻塞了UI线程,可能导致应用提示无响应状态。本节将学习如何通过AsyncTask在后台处理位图,并说明如何处理并发问题。


        使用异步任务
        异步任务类提供了一种简单的方法,用于在后台线程中执行某些工作,并将结果发布到UI线程中。要使用异步任务,需要创建AsyncTask的子类,并重写其中一些方法,下面是一个通过AsyncTask下载大图到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);
            }
        }
    }
}
        使用WeakReference存贮ImageView可以让AsyncTask不阻止ImageView和其他的资源被垃圾回收,不过,当异步任务结束时不保证ImageView仍然存在,所以必须在onPostExecute()中检查引用。ImageView可能已经不存在,比如说,客户从当前Activity中导航离开,或者在异步任务结束之前配置发生改变(屏幕方向发生改变)。
        异步下载位图只需要创建一个该异步任务的子类,并执行即可。


        处理并发
        如上一节所示,常用组件如ListView和GridView与异步任务结合使用时可能引起另外一个问题,为了高效使用内存,当组件滚动时,组件会循环使用其子视图,如果每个视图都触发一个AsyncTask,则无法确定当他们结束时,与之相关联的视图是否正被另外一个视图循环使用着。而且,无法保证异步任务启动执行的顺序与其结束执行的顺序一致。

        这篇博客对多线程操作的并发问题作进一步讨论,并提供一种方案用于在AsynTask中存贮一个最近使用的ImageView的引用,这个引用会在异步任务结束时被再次检查。通过相似的方法,上一节中讨论的异步任务可以被扩展至如下一种相似的模式。
        创建一个专用的Drawable子类用于存储一个引用到工作task中,这样,一个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相关联了,如果有,那么这个异步任务会调用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<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组件中显得更加合适,也适用于其他任何循环复用子视图的组件。使用时只需要简单调用loadBitmap即可。例如在GridView的实现类中,这个过程只要在后台的适配器中执行即可。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值