在"高效的加载图片"课程中咱们讨论了BitmapFactory.decode方法,可是若是图片资源是从网络或者外存(或者其余非内存的存储位置)读取 的,那么咱们不该该在UI线程中执行加载图片的操做。由于图片加载的时间是不可预期的并由众多的因素所决定(外存储器或者网络的读取速度,图片的大小,CPU的性能,等等)。若是加载图片的操做阻塞了UI线程,系统会把您的应用程序标识为"未响应"并提示用户关闭它(详情请参见响应式设计)。 html
这一节将要介绍AsyncTask的使用,并介绍怎样处理并发问题。 android
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
一般,当使用像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()方法中调用。