1. 程式人生 > >Android 有效地展示圖片(二)Processing Bitmaps Off the UI Thread 在ui執行緒外處理bitmap

Android 有效地展示圖片(二)Processing Bitmaps Off the UI Thread 在ui執行緒外處理bitmap

原文連結http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html

我們在上節課討論了BitmapFactory.decode系列的方法,但是如果原圖的資料需要從硬碟或者網路或者別的途徑而非記憶體讀取時,那麼這個方法就不應該放在UI執行緒裡執行,因為讀取該圖片的時間收到多方面影響,(如:網速或者硬碟讀取速度的大小,圖片的大小,cpu的速度大小等等)。如果這些因素中的任何一個堵住了UI執行緒,那麼你的程式就會出現“無反應”,使用者也就可以關閉它。

這節課我們將學習怎樣在後臺用AsyncTask來處理圖片並教你如何處理併發事件。

Use an AsyncTask 用AsyncTask類

AsyncTask類可以方便的實現在後臺執行緒中處理一些工作並把結果傳回UI執行緒。要用此類,我們需要建立該類的子類並複寫其中的方法。下面就是一個用AsyncTask 和 decodeSampledBitmapFromResource()來下載圖片到一個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);
            }
        }
    }
}



在AsyncTask執行時,ImagaeView有可能會被垃圾回收機制回收,為了不阻礙垃圾回收機制,所以就把ImageView放
入了軟引用。這樣的話當任務執行完畢時,ImageView 可能就不在那裡了,所以你必須在onPostExecute()方法中檢查
該引用。有時在任務完成之前,比如當用戶已經離開activity或者有別的變化,ImageView 就可能不存在了。
非同步的載入圖片通常另起一個任務執行:

public void loadBitmap(int resId, ImageView imageView) {
    BitmapWorkerTask task = new BitmapWorkerTask(imageView);
    task.execute(resId);
}

 
 

處理併發

普通的檢視元件如 ListViewGridView 在和上述的AsyncTask聯合使用時帶來了其他的問題。為了有效利用記憶體,當用戶滾動螢幕時,這些元件都是在迴圈利用他們的子View.如果每個子View觸發一個AsyncTask,那麼當該AsyncTask完成時,誰都沒法保證當初觸發它的那個View沒有被重複利用去繪製另一個子View。況且,這些AsyncTask開始的順序和它們完成的順序並不一定一致。 建立一個專門的Drawable類儲存AsyncTask的引用。在此例中,用的是一個BitmapDrawable類,這樣的話,在任務完成時一個佔位的圖片就會顯示在ImageView上。
private 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繫結,暫時先不用理會if條件
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);
    }
}
然後我們在task執行 onPostExecute方法時不僅要判斷ImageView相對應,還要判斷這個imageview繫結的task是不是當前的task
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;
}

上述方法是得到image view繫結的task方法。那麼我們就要更正Task中的onPostExecute方法如下
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);
            }
        }
    }
}
ok,現在我們在回看上面loadbitmap方法中的 if條件, if判斷的是這樣一個方法

 
   
  
 
  
 

public static boolean cancelPotentialWork(int data, ImageView imageView) {
    final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);

    if (bitmapWorkerTask != null) {
        final int bitmapData = bitmapWorkerTask.data;
        // 如果bitmap還沒有被設定或者bitmapdata資料和正在執行的data的不一樣
        if (bitmapData == 0 || bitmapData != data) {
            // 那麼就把之前的task取消掉
            bitmapWorkerTask.cancel(true);
        } else {
            //如果一樣那麼就是說當前的task就是image的task 就無需重新綁定了
            return false;
        }
    }
    // No task associated with the ImageView, or an existing task was cancelled
    return true;
}


上面方法就是說在listview中的一條imageView準備發起task時,首先檢查imageview有沒有繫結的另外的task,如果有綁定了,那麼就應該先取消以前的,在執行當前的,如果繫結的就是當前的那麼就不用取消了。這就是為什麼我們還要在上述onPostExecte方法中加上如下程式碼

protected void onPostExecute(Bitmap bitmap) {
        if (isCancelled()) {
            bitmap = null;
        }



}




這樣的實現現在就適合ListView控制元件和GridView控制元件,也適合與其他的迴圈利用子view的控制元件,通常在將圖片設定到ImageView上的時候呼叫loadBitmap,比如,在GridView中,就應該在getView()方法中呼叫此方法。