Displaying Bitmaps Efficiently (三)

在UI线程之外处理图片

——Android官网原文翻译

在"高效的加载图片"课程中咱们讨论了BitmapFactory.decode方法,可是若是图片资源是从网络或者外存(或者其余非内存的存储位置)读取 的,那么咱们不该该在UI线程中执行加载图片的操做。由于图片加载的时间是不可预期的并由众多的因素所决定(外存储器或者网络的读取速度,图片的大小,CPU的性能,等等)。若是加载图片的操做阻塞了UI线程,系统会把您的应用程序标识为"未响应"并提示用户关闭它(详情请参见响应式设计)。 html

这一节将要介绍AsyncTask的使用,并介绍怎样处理并发问题。 android

1.使用AsyncTask

AsyncTasks类提供了简单的方式用来在后台线程执行一些任务,并把结果传递到到UI线程。要使用这个类,就要建立一个继承了该类的子类,并重写一些方法。这里有一个使用AsyncTask和decodeSampledBitmapFromResource()(见上一节高效的加载图片)加载大图片到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以及任何ImageView引用的内容不被GC回收。咱们这里并不能保证当任务结束的时候ImageView的引用仍然可用,因此您也必须在onPostExecute()中检查引用。举个例子,当用户从当前Activity跳转到其余Activity或者某个设置在任务结束前发生变化的话,这个ImageView可能会不在存在。 并发

若是要一步的加载一张图片,只需建立一个AsyncTask对象并调用execute方法。 async

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

2处理并发

一般,当使用像ListView或者GridView这类UI组件同时的使用上一节的AsyncTask加载图片时会遇到问题。为了有效的使用内存,这些控件会在滚动时回收子View,若是其中的每个View都触发了一个AsyncTask,那么咱们不能保证这些AsyncTask已经完成了,那么该相关联的View实际上并无被回收。此外,咱们也不能保证多个AsyncTask结束时的顺序会和它们开始时的顺序同样。 性能

"使用多线程提高性能"这篇博客深刻的讨论了并发性,而且提出了一个解决方案——ImageView保留一个最近AsyncTask的引用,该AsyncTask在任务完成时能够被检查到。使用相同的方法,上一节中的AsyncTask可使用如下相同的模式进行扩展。 ui

建立一个专用的Drawable子类来保存对AsyncTask的引用,既然这样,一个BitmapDrawable对象被用来当任务结束时在ImageView上显示一个占位图片。 this

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();
    }
}

在执行BitampWorkerTask以前,您须要建立一个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);
    }
}

示例代码中使用的cancelPotentailWork方法,用来检查是否有其余正在运行的任务关联了ImageView。若是存在,它将尝试使用cacel()方法取消上一个任务。在少数状况下,新的任务数据与已经存在的任务的数据同样的话,则什么都不须要发生。如下是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;
        }
    }
    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中像其余组件回收它们的子views同样使用了。当您想要让一张图片在ImageView中显示时,只需简单的调用loadBitmap便可。举个例子,若是用来在一个GridView中显示图片的话,那么这个方法能够在GridView的adapter的getView()方法中调用。

相关文章
相关标签/搜索