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