1.如何在非UI线程中处理位图
前面一篇文章已经介绍了BitmapFactory.Decode方法,不应该在主线程中被调用(除非位图的来源是内存),因为加载位图的时间是不可预知的,而且她还依赖了很多的其他因素,例如磁盘的读取时间,CPU的功率,图片的大小等因素,无论上述的任何人一个因素导致了UI线程被阻塞,那么系统将应用程序标记为无响应状态,此时用户有权关闭应用。
本文将引导如何在异步线程中处理图片的解码到内存的过程,如何处理线程并发的问题
2.使用AsnycTask
AsyncTask让我们可以使用简单的方法就可以在后台线程中执行一些任务,并将处理的结果反馈给UI线程,使用该类需要创建一个它的子类,并且复写一些方法下面将演示如何使用AsyncTask和decodeSampleBitmapFromResuource(),为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仍然可以用(例如用户可能再执行后台任务的时候离开当前页面,或者对页面进行其他的操作导致无法找到ImageView)所以必须要在onPostExcute中检查是否可用。
只需要像下面一样创建一个任务,并且执行该任务,就可以实现异步加载位图
public void loadBitmap(int resId, ImageView imageView) {
BitmapWorkerTask task = new BitmapWorkerTask(imageView);
task.execute(resId);
}
并发处理
某些共通视图组件,例如ListView,GridView 如果像上面那样将他们与AsyncTask连用的时候就会遇到新的问题,为了高效的利用内存,当用户在上述组件中进行滚动的时候,这些组件将会重用其中的视图,如果每个子视图触发一个AsyncTask任务的时候,那么我们将无法保证在任务执行完成的时候,触发该任务的子视图没有被其子视图使用,此时我们也无法保证任务执行的时候完成的顺序和调用的顺序的一致性
在使用ImageView的地方保存一个指向AysncTask最近的引用,当这个任务完成的时候可以检查AsyncTask,使用如之类似的方法,我们可以将前面使用的AsyncTask进行拓展来达到我们的目的。
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之前我们可以先创建一个AysncDrawble,然后将他绑定到所需的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);
}
}
在上述的方法中的canclePotentialWork方法用于检查是否有其他的任务关联到当前的ImageView上,如果有其他的任务关联到当前的ImageView 那么久可以调用cancle()取消当前绑定的任务,在某些情况下,新任务中的data和已经存在的data相同,此时就不需要做任何处理,下面是canclepotenialWorker的具体实现的方法
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;
}
}
// 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;
}
最后一步是更新BitampWorkerTask中的onPostExcute()方法,在改方法中检查该任务是否需要已经被取消,而且还要检查当前任务与关联的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组件,以及任何其他会使用重复使用图片的组件,具体做法也很简单,只需要在原来的ImageView设值得地方调用loadBitmap就可以了,例如在实现GridView的时候,在返回的Adapter中的getView回调方法中调用上述的方法。
转载自:http://yhz61010.iteye.com/blog/1848811