高效地加載圖片(二) 在UI線程外處理圖片

在使用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;
}
最後一步是在BitmapWorkerTask的onPostExecute()方法中更新UI,此時須要檢查異步任務是否被停止了,而且只有在當前任務與ImageView綁定的任務匹配時才設置獲取到的Bitmap.
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);
            }
        }
    }
}
相關文章
相關標籤/搜索