您的位置:首页 > 产品设计 > UI/UE

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

2014-11-12 09:57 465 查看
        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的实现类中,这个过程只要在后台的适配器中执行即可。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐