在UI主线程外处理Bitmap
BitmapFactory.decode* 系列的方法,讨论的是怎么样去高效的加载大图片,但是不管图片数据的来源,这些方法都不应该在UI主线程上使用。因为这些方法耗费的时间是不可预估的,图片加载耗费的时间依赖于很多的因素(网络或硬盘的读写速度,图片的大小,手机CPU的处理能力等等)。在程序中,只要其中一个图片加载任务阻塞了UI主线程,那么你的应用将会无响应(ANR,不能再与用户进行交互了),Android系统会显示通知用户,用户将会选择关闭你的应用,这是一项非常不好的用户体验。
出于这种原因,你可以使用
AsyncTask 来在UI主线程外的线程中处理图片加载,但还有一个问题,要妥善处理好并发的问题,下面将介绍这两种问题的处理方法
使用AsyncTask
AsyncTask
类为我们提供了一种很好的方式来在UI主线程之外的线程中执行一些任务,并且把任务的结果返回到UI主线程上。你需要继承
AsyncTask
类并重载一些提供的方法来使用这种异步任务方式,下面是一个示例程序
class BitmapWorkerTask extends AsyncTask {
private final WeakReference imageViewReference;
private int data = 0;
public BitmapWorkerTask(ImageView imageView) {
// Use a WeakReference to ensure the ImageView can be garbage collected
imageViewReference = new WeakReference(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.
// 此方法运行在UI主线程上
@Override
protected void onPostExecute(Bitmap bitmap) {
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = imageViewReference.get();
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
使用弱引用去保存
ImageView
对象是为了确保
AsyncTask
不会去影响
ImageView
的使用和垃圾回收器能够回收
ImageView
引用的对象。不能保证当任务完成的时候
ImageView
引用的对象没有被回收,所以呢你必须在
onPostExecute()
方法中进行检查是否为null。
ImageView
对象可能会不存在了,例如,在一个任务结束前,用户可能已经离开了当前的用户界面(Activity被destroy)或者一个配置项(例如横竖屏切换)发生改变 ,所以在使用的时候必须对
ImageView
对象做必要的检测。
public void loadBitmap(int resId, ImageView imageView) {
BitmapWorkerTask task = new BitmapWorkerTask(imageView);
task.execute(resId);
}
处理并发问题
像
ListView和
GridView 这种常见的视图组件,结合
AsyncTask
一起使用的时候会产生另外一个问题。为了尽可能的提高内存的使用效率,当用户滚动组件时,这类视图组件会循环利用它的子视图,如果每个子视图都触发一个
AsyncTask ,那么将不会有保证相关联的视图是否已经回收供另外一个子视图循环使用了。而且,异步任务的执行顺序并不意味着任务的执行结束时的顺序也是这样的。结合这两种原因,引发的这个并发问题有可能会导致图片设置到子视图上时会发生错位。
//存储着一个与之对应的task, 在任务还在执行时充当一个占位符,当任务完成时,随之被替换
static class AsyncDrawable extends BitmapDrawable {
private final WeakReference bitmapWorkerTaskReference;
public AsyncDrawable(Resources res, Bitmap bitmap,
BitmapWorkerTask bitmapWorkerTask) {
super(res, bitmap);
bitmapWorkerTaskReference =
new WeakReference(bitmapWorkerTask);
}
public BitmapWorkerTask getBitmapWorkerTask() {
return bitmapWorkerTaskReference.get();
}
}
public void loadBitmap(int resId, ImageView imageView) {
if (cancelPotentialWork(resId, imageView)) {
final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
// mPlaceHolderBitmap 引用的就是默认要显示的图片对象
final AsyncDrawable asyncDrawable =
new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
imageView.setImageDrawable(asyncDrawable);
task.execute(resId);
}
}
cancelPotentialWork
方法是用来检测与
ImageView
相关联的另外一个任务是否正在执行,如果是的话,将会通过调用
cancel()
方法来
关闭这个 任务。只有极少部分的情况下两个任务的图片数据会相等,如果相等的话,那也不会出现什么并发问题了,我们也不需要做任何的处理
public static boolean cancelPotentialWork(int data, ImageView 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;
}
}
// No task associated with the ImageView, or an existing task was cancelled
return true;
}
下面是
getBitmapWorkerTask()
方法的定义
private static BitmapWorkerTask getBitmapWorkerTask(ImageView 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()
方法中我们就需要检测任务是否被取消了,还有需要检测下当前的任务是否是与imageView相关联的任务
class BitmapWorkerTask extends AsyncTask {
...
@Override
protected void onPostExecute(Bitmap bitmap) {
// if task cancelled , this method is never invoked
// why check here ?
if (isCancelled()) {
bitmap = null;
}
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = imageViewReference.get();
final BitmapWorkerTask bitmapWorkerTask =
getBitmapWorkerTask(imageView);
// 判断当前task是否是相关联的task
if (this == bitmapWorkerTask && imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
前面的ImageView加载的实现方式适用于像
ListView
and
GridView
这类的UI视图组件,我们可以在Adapter中的
getView()
方法中调用
loadBitmap
方法
高效Bitmap加载与AsyncTask
本文探讨了如何在Android应用中高效地加载大图片,并避免阻塞UI主线程导致的用户体验问题。通过使用AsyncTask在后台线程处理图片加载,并解决ListView等组件引起的并发和错位问题。
4545

被折叠的 条评论
为什么被折叠?



