本文译自:http://developer.android.com/training/displaying-bitmaps/process-bitmap.html
如果源图片来自磁盘或网络(或者其他任何内存以外地方),那么在“高效的加载大位图”一文中所讨论的BitmapFactory.decode*方法就不应该在主UI线程中执行。加载图片所需的时间是不可预知的,并且还要依赖各种因素(如磁盘或网络的读取速度、图片的尺寸、CPU的处理能力等)。如果这些因素中有一个阻塞了UI线程,那么系统把你的应用程序标记为非响应程序,并且用户可以选择关闭它。
本文讨论如何使用AsyncTask在后台线程中处理位图,以及如何处理并发问题。
使用AsyncTask
AsyncTask类为在后台线程中执行某些任务提供了比较容易的方法,并且它会把执行结果返回给UI线程。要使用它,就要创建一个子类,并重写相应的方法。以下是使用AsyncTask类和decodeSampleBitmapFromResource()方法把大位图加载到一个ImageView中的例子:
classBitmapWorkerTaskextendsAsyncTask<Integer,Void,Bitmap>{
private final WeakReference<ImageView> imageViewReference;
private int data = 0;
public BitmapWorkerTask(ImageView imageView) {
// Use aWeakReference to ensure the ImageView can be garbage collected
imageViewReference = new WeakReference<ImageView>(imageView);
}
// Decode image inbackground.
@Override
protected Bitmap doInBackground(Integer... params) {
data = params[0];
return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
}
// Once complete, see ifImageView 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对象还存在,因此你必须在onPostExecute()方法中检查该引用。例如,如果用户离开该Activity,或者在任务完成之前,相关的配置发生了变化,那么ImageView就可能不再存在。
要异步的加载位图,只需简单的创建一个新的任务并执行它:
publicvoidloadBitmap(int resId,ImageViewimageView){
BitmapWorkerTask task = new BitmapWorkerTask(imageView);
task.execute(resId);
}
处理并发
通常当诸如ListView和GridView这样的View组件跟AsyncTask结合使用时,就会引入另外的问题。为了提高内存的使用效率,这些组件会在用户滚动时会回收子View。如果每个子View都触发一个AsyncTask任务,那么不能够保证AsyncTask任务被执行完成之前,相关的子View不被回收。此外,也不能够保证异步任务按照启动顺序来完成。
创建一个专用的Drawable子类来存储工作任务的引用。在本文中使用BitmapDrawable,以便在任务执行完成时,其对应的图片能够被显示在ImageView中。
staticclassAsyncDrawableextendsBitmapDrawable{
private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
public AsyncDrawable(Resources res, Bitmap bitmap,
BitmapWorkerTask bitmapWorkerTask) {
super(res, bitmap);
bitmapWorkerTaskReference =
new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
}
public BitmapWorkerTaskgetBitmapWorkerTask() {
return bitmapWorkerTaskReference.get();
}
}
在执行BitmapWorkerTask之前,你要创建一个AsyncDrawable对象,并把它跟目标的ImageView对象绑定:
publicvoidloadBitmap(int resId,ImageViewimageView){
if (cancelPotentialWork(resId, imageView)) {
final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
final AsyncDrawableasyncDrawable =
new AsyncDrawable(getResources(), mPlaceHolderBitmap,task);
imageView.setImageDrawable(asyncDrawable);
task.execute(resId);
}
}
上例代码中的cancelPotentialWork方法会检查其他正在运行的任务是否已经跟该ImageView对象相关联。如果关联了,它会尝试调用cancel()方法来取消之前的任务。在少数场合,新任务的数据会跟既存的任务相匹配,并且不会再有其他的需求发生。以下是cancelPotentialWork方法的实现:
publicstaticbooleancancelPotentialWork(int data,ImageViewimageView){
final BitmapWorkerTaskbitmapWorkerTask =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 withthe ImageView, or an existing task was cancelled
return true;
}
一个辅助的方法:getBitmapWorkerTask()被用于获取与上述任务相关联的ImageView对象:
privatestaticBitmapWorkerTaskgetBitmapWorkerTask(ImageViewimageView){
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对象:
classBitmapWorkerTaskextendsAsyncTask<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组件,以及其他任何回收子View的组件。简单的调用loadBitmap方法,就可以把一个图片设置给ImageView对象。例如,在GridView中会在适配器支持的getView方法中实现这种模式。