原文地址:http://developer.android.com/training/displaying-bitmaps/process-bitmap.html
在非UI线程中处理图片
BitmapFactory.decode*方法,已在Load Large BItmaps Efficiently 章节中讨论了,如果资源数据从磁盘或是网络获取的话不应该在UI线程中执行。图片数据的获取所用的时间是未知的,依赖于很多条件。如果其中的一个堵塞了主线程,会导致应用无响应。
这节课通过展示使用AsyncTask在后台线程中处理图片加载的流程,来解决死锁的问题。
使用AsyncTask
AsyncTask类提供了一个简单的方法让一些任务在后台线程中执行,并把结果返回到UI线程中。下面是一个使用AstncTask和decodeSampleBitmapFromResource()加载一个大图到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); } } } }
使用WeakReference存储ImageView,可以使AsyncTask不会阻止ImageView被垃圾回收。因为无法保证在任务结束后ImageView还存在,所以在onPostExecute()方法中也必须要校验。
并发处理
如果在ListView和GridView这样的组件中像上一节那样使用AsyncTask会导致另一种结果。为了有效的利用缓存,这些组件在滚动的过程中会复用子控件。如果每个子控件只出发一个任务,那个无法避免当这个任务完成的时候,这个任务关联的控件正用于展示其他子视图。
在博客MultithreadingforPerformance中讨论并发行为,并给出了一个解决方案,通过ImageView存储一个最近的任务AsyncTask,这样当任务完成时能够被处理。
创建一个专门的Drawable子类存储回调的任务。这样当任务返回时就可以利用这个图片来展示在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(); } }
在执行BitmapWorkerTask任务前,需要创建一个AsyncDrawable并绑定到目标ImagView
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); } }
cancelPotentialWork是取消绑定目标ImageView的上一个任务
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()方法需要判断任务的cancelled状态
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); } } } }