在使用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); } } } }