有效地展示大位图,不应该在主UI线程上执行如果源数据从磁盘读取或网络位置(或真正内存以外的任何来源)。加载数据需要的时间是不可预测的,取决于多种因素(阅读从磁盘或网络,速度大小的图像,CPU,等等)。如果其中一个任务阻塞UI线程,系统应用程序没有响应,用户选择关闭。
So为了用户体验,通过在后台线程处理位图,这里使用AsyncTask,并且展示如何处理并发问题。
使用异步任务(AsyncTask)
AsyncTask类提供了简单方法后台执行任务,UI线程返回结果。如何使用呢,创建一个子类继承它,下面提供一个示例,如何加载大图片:
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private int data = 0;
public BitmapWorkerTask(ImageView imageView) {
// 使用若引用,确保可以被回收
imageViewReference = new WeakReference<ImageView>(imageView);
}
// 后台解析
@Override
protected Bitmap doInBackground(Integer... params) {
data = params[0];
//上篇说到请参考http://blog.youkuaiyun.com/hello_12413/article/details/48261503
return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
}
//完成加载
@Override
protected void onPostExecute(Bitmap bitmap) {
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = imageViewReference.get();
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
上面的弱引用是为了确保能被回收。无法保证当任务完成的时候ImageView还在,所以在onPostExecute需要核对。
开始异步加载位图,简单地创建一个新的任务并执行:
public void loadBitmap(int resId, ImageView imageView) {
BitmapWorkerTask task = new BitmapWorkerTask(imageView);
task.execute(resId);
}
处理并发
普通视图比如ListView,GridView引入了另一个问题,使用时结合 AsyncTask。为了有效地使用内存资源,这些组件回收item当用户滑动的时候。如果每个item触发一个 AsyncTask,没法保证当任务完成的时候这个item没有被回收用于其他item。也没法保证任务启动的顺序和完成的顺序一至。
首先写一个子类继承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();
}
}
首先在执行任务之前,要新建一个AsyncDrawble对象,并绑定到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);
}
}
然后因为列表可能上下翻动,所以需要判断任务是否已经存在,如果是那么会调用当前的cancel(),方法取消
public static boolean cancelPotentialWork(int data, ImageView imageView) {
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (bitmapWorkerTask != null) {
final int bitmapData = bitmapWorkerTask.data;
// If bitmapData is not yet set or it differs from the new data
if (bitmapData == 0 || 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;
}
最后一步在onPostExecute()更新UI,检查任务是否取消,是否与当前任务匹配,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 这样的控件,可以在Adapter的getView方法里显示图片。