原文链接http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
我们在上节课讨论了BitmapFactory.decode系列的方法,但是如果原图的数据需要从硬盘或者网络或者别的途径而非内存读取时,那么这个方法就不应该放在UI线程里执行,因为读取该图片的时间收到多方面影响,(如:网速或者硬盘读取速度的大小,图片的大小,cpu的速度大小等等)。如果这些因素中的任何一个堵住了UI线程,那么你的程序就会出现“无反应”,用户也就可以关闭它。
这节课我们将学习怎样在后台用AsyncTask来处理图片并教你如何处理并发事件。
Use an AsyncTask 用AsyncTask类
AsyncTask类可以方便的实现在后台线程中处理一些工作并把结果传回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);
}
}
}
}
在AsyncTask执行时,ImagaeView有可能会被垃圾回收机制回收,为了不阻碍垃圾回收机制,所以就把ImageView放
入了软引用。这样的话当任务执行完毕时,ImageView 可能就不在那里了,所以你必须在onPostExecute()方法中检查
该引用。有时在任务完成之前,比如当用户已经离开activity或者有别的变化,ImageView 就可能不存在了。
异步的加载图片通常另起一个任务执行:
public void loadBitmap(int resId, ImageView imageView) {
BitmapWorkerTask task = new BitmapWorkerTask(imageView);
task.execute(resId);
}
处理并发
普通的视图组件如 ListView和GridView 在和上述的AsyncTask联合使用时带来了其他的问题。为了有效利用内存,当用户滚动屏幕时,这些组件都是在循环利用他们的子View.如果每个子View触发一个AsyncTask,那么当该AsyncTask完成时,谁都没法保证当初触发它的那个View没有被重复利用去绘制另一个子View。况且,这些AsyncTask开始的顺序和它们完成的顺序并不一定一致。
创建一个专门的Drawable类存储AsyncTask的引用。在此例中,用的是一个BitmapDrawable类,这样的话,在任务完成时一个占位的图片就会显示在ImageView上。
private 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绑定,暂时先不用理会if条件
上述方法是得到image view绑定的task方法。那么我们就要更正Task中的onPostExecute方法如下
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);
}
}
然后我们在task执行
onPostExecute方法时不仅要判断ImageView相对应,还要判断这个imageview绑定的task是不是当前的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;
}
上述方法是得到image view绑定的task方法。那么我们就要更正Task中的onPostExecute方法如下
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);
}
}
}
}
ok,现在我们在回看上面loadbitmap方法中的
if条件,
if判断的是这样一个方法
public static boolean cancelPotentialWork(int data, ImageView imageView) {
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (bitmapWorkerTask != null) {
final int bitmapData = bitmapWorkerTask.data;
// 如果bitmap还没有被设置或者bitmapdata数据和正在执行的data的不一样
if (bitmapData == 0 || bitmapData != data) {
// 那么就把之前的task取消掉
bitmapWorkerTask.cancel(true);
} else {
//如果一样那么就是说当前的task就是image的task 就无需重新绑定了
return false;
}
}
// No task associated with the ImageView, or an existing task was cancelled
return true;
}
上面方法就是说在listview中的一条imageView准备发起task时,首先检查imageview有没有绑定的另外的task,如果有绑定了,那么就应该先取消以前的,在执行当前的,如果绑定的就是当前的那么就不用取消了。这就是为什么我们还要在上述onPostExecte方法中加上如下代码