异步线程处理bitmap

https://developer.android.com/training/displaying-bitmaps/process-bitmap.html

高效加载大bitmap课程讨论过,当从磁盘或者网络读取数据(或者其他没有在内存的数据)时,不应该在主线程执行BitmapFactory.decode*方法。因为此时加载数据花费的时间是不确定的,依赖于很多因素(磁盘读取速度、网速、图片大小、或者CPU的功率等等),如果这些任务其中一个阻塞了UI线程,系统就会把应用标志为无响应,用户会有一个关闭它的选项(更多信息请参考响应性设计)

这节课将会带领你使用AsyncTask在后台线程处理bitmap,并且告诉你应该怎么处理并发问题

使用AsyncTask

AsyncTask提供了一种简单的方式在后台执行一些工作后在UI线程回调结果。使用AsyncTask,需要创建一个子类并且覆盖一些方法,下面是一个使用AsyncTask和decodeSampleBitmapFromSource()加载大图到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或者其它它引用的任何被GC回收。也就是当task结束时无法保证ImageView仍然存在,所以在onPostExecute中需要检查引用是否存在。比如,当用户已经离开activity或者task执行结束前全局配置更改了,ImageView可能已经不存在了。

为了开始异步加载这个bitmap,需要创建一个新的task并执行它:

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

处理并发

通用的view组件如ListView和GridView如果跟上面说的结合AsyncTask使用的话会有另外的问题。为了使得内存的高效使用,这些组件会在用户滚动时回收子views。如果每一个子view都用AsyncTask,当AsyncTask执行完时无法保证它关联的view是否已经被回收并已经显示其它的子view。此外,也无法保证AsyncTask开始和完成的顺序。

多线程性能这篇博客进一步讨论了怎么处理并发,并且提供了解决方案,当task执行完时ImageView应该在哪里存储会被AsyncTask检查的引用。使用一种简单的方法,使用类似的方法,上一节说的AsyncTask可以类似的方式扩展。

创建一个专用的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关联的正在执行的task。如果有,它会调用cancel去取消之前task的执行。少数情况下,新的task数据会完全匹配已存在的task所以不需要做进一步的处理。下面是cancelPotentialWork的实现:

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关联的task

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()方法中更新,使用BitmapWorkerTask检查task是否已经被取消,当前task是否同ImageView关联的task匹配

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组件和其它任何需要回收子views的组件,在你需要在ImageView设置image的地方简单调用loadBitmap。如在GridView实现中它应该在adapter的getView方法中调用。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值