在使用BitmapFactory.decode*方法解析图片时,若是要读取的图片在SD卡上或者网络位置(或者任何内存意外的位置),则该过程不能在主线程中执行.
由于这个过程所耗费的时间是不肯定的,这个时间跟多种因素有关(从磁盘或者网络读取数据的速度,图片的大小,CPU的工做效率等).若是这其中的某一项阻塞了UI线程的执行,则就会出现ANR异常.html
使用异步任务处理图片java
AsyncTask为咱们提供了在后台线程进行处理工做,并将处理的结果发布到UI线程的方法.android
要使用AsyncTask,须要建立它的子类并覆写其中的方法.这里有一个使用AsyncTask处理图片的异步任务:网络
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { private final WeakReference<ImageView> imageViewReference; private int data = 0; public BitmapWorkerTask(ImageView imageView) { // 此处使用弱引用,以保证这个ImageView能够被垃圾回收器回收 imageViewReference = new WeakReference<ImageView>(imageView); } // 在后台解析图片 @Override protected Bitmap doInBackground(Integer... params) { data = params[0]; return decodeSampledBitmapFromResource(getResources(), data, 100, 100)); } // 在解析图片成功后,检查ImageView是否依然存在 // 若是存在,则将解析获得的Bitmap设置到ImageView中显示 @Override protected void onPostExecute(Bitmap bitmap) { if (imageViewReference != null && bitmap != null) { final ImageView imageView = imageViewReference.get(); if (imageView != null) { imageView.setImageBitmap(bitmap); } } } }
要异步加载图片,只须要建立一个异步任务而且开启该任务:并发
public void loadBitmap(int resId, ImageView imageView) { BitmapWorkerTask task = new BitmapWorkerTask(imageView); task.execute(resId); }
处理并发异步
当ListView和GridView等组件与上述使用AsyncTask进行图片加载结合使用时,会出现另一个问题.为了保证高效地使用内存,这些组件会在滑动时不断将内部的View进行复用.若是这些组件中的每一个子View都开启了一个异步任务,咱们不能保证在异步任务完成时,与之关联的View依然存在,这个View可能已经被复用做了其余的子View.另外,咱们也不能肯定异步任务完成的顺序与开启的顺序一致.async
建立一个专用的Drawable的子类,用于存放一个异步任务的引用.这样,在异步任务执行过程当中,ImageView中会显示一个占位符.ide
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而且将它绑定到目标ImageView上.this
public void loadBitmap(int resId, ImageView imageView) { // 检查是否已经有与当前ImageView绑定的任务存在而且执行了 // 若是已经有任务与当前ImageView绑定,则中断原先绑定的任务 // 而且将新的任务绑定到ImageView if (cancelPotentialWork(resId, imageView)) { // 建立新的BitmapWorkerTask final BitmapWorkerTask task = new BitmapWorkerTask(imageView); // 建立占位用的AsyncDrawable final AsyncDrawable asyncDrawable = new AsyncDrawable(getResources(), mPlaceHolderBitmap, task); // 将占位用的AsyncDrawable设置到ImageView中 imageView.setImageDrawable(asyncDrawable); // 开始执行任务 task.execute(resId); } }
cancelPotentialWork方法是为了检查是否已经有异步任务与当前ImageView绑定而且执行了,若是确实有异步任务已经与当前ImageView绑定了,则调用cancel()方法结束那个异步任务(由于这个异步任务对应的ImageView已经被复用做了其余的子View,则这个异步任务获取到的Bitmap应该被废弃).线程
而在少数状况下,新的任务须要加载的图片可能与原先的任务相同,则不开启新的任务也不中断原先的任务,让原先的任务继续执行.
public static boolean cancelPotentialWork(int data, ImageView imageView) { final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); if (bitmapWorkerTask != null) { // 获取到原BitmapWorkerTask的data值 final int bitmapData = bitmapWorkerTask.data; // 若是原异步任务中的data值没有设置或者与当前任务的data不一样 // 则中断原异步任务 if (bitmapData == 0 || bitmapData != data) { bitmapWorkerTask.cancel(true); } else { // 若是原异步任务的data值与当前任务的data值相同 // 则返回false,此时不开启新的任务 return false; } } // 若是bitmapWorkerTask为空,则原先没有异步任务与当前ImageView绑定 // 返回true,开启新的异步任务加载图片 return true; }
getBitmapWorkerTask()用于根据特定的ImageView来获取对应的异步任务.
private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { if (imageView != null) { //从ImageView获取Drawable final Drawable drawable = imageView.getDrawable(); if (drawable instanceof AsyncDrawable) { //若是获取到的Drawable类型是AsyncDrawable,则强赚 final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; //从AsyncDrawable获取到绑定的异步任务 return asyncDrawable.getBitmapWorkerTask(); } } return null; }
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { ... @Override protected void onPostExecute(Bitmap bitmap) { if (isCancelled()) { // 若是异步任务被停止了,则获取到的Bitmap废弃 bitmap = null; } if (imageViewReference != null && bitmap != null) { // 若是ImageView的弱引用不为空,而且获取到了Bitmap // 则从弱引用获取到ImageView final ImageView imageView = imageViewReference.get(); // 根据ImageView获取异步任务 final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); // 将当前任务和ImageView绑定的任务对比 if (this == bitmapWorkerTask && imageView != null) { //当前任务与绑定的任务相同时,才向ImageView中设置Bitmap imageView.setImageBitmap(bitmap); } } } }