Android高效顯示圖片詳解(二)

    

       實際的使用環境中,若是圖片來源是SD卡或者網絡,那那麼加載圖片的過程必定不要放在UI線程中,這樣會嚴重的阻塞UI線程,出現ANR,程序就廢了。所以咱們首先要實現異步加載。

第一步:利用AsyncTask實現圖片的異步加載

java

將decodeSampledBitmapFromResource方法放入Task的doInBackground中後臺執行。不熟悉AsyncTask的同窗能夠學習AsyncTask的相關知識,這裏再也不過多介紹。緩存

代碼:網絡

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必要時候的垃圾回收。不然可能會出現內存泄露,同時,咱們必定要在Task執行完畢後對ImageView的存在性進行判斷,由於不能保證Task執行完畢後,ImageView還會存在。併發

下來咱們按照下面的代碼就可使用這個Task了:異步

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

第二步:處理併發狀況async


ListView與GridView這種多子視圖的控件會出現兩個問題,

ide

第一,一個ListView會有衆多的ChildView,爲了更高效的利用內存,控件會自動回收掉被用戶滑動過去,不在當前有顯示的ChildView,若是每個ChildView都開啓一個Task去加載圖片,這樣就不能保證開啓Task的ChildView在Task執行完畢後沒有被回收掉(頗有可能用戶滑動到其餘地方去了)。


第二,由於每張圖片的處理時間是不一樣的,所以一樣不能保證加載完成的次序與開始的次序一致。


下來咱們開始着手解決這些問題,咱們要讓ImageView與Task造成一種綁定的關係。

咱們先來建立一個特殊的Drawable,這個Drawable有兩個功能,一個是與Task造成一種綁定的關係,另外也充當了ImageView的臨時佔位圖像,該Drawable的代碼以下:函數

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();
    }
}

在該Drawable中經過弱引用能與對應的Task造成一種一一對應的捆綁關係。學習

咱們能夠這樣使用它,在執行Task以前,先建立一個對應的Drawable,並把它當成將要呈現實際圖片的ImageView佔位圖片,同時也與ImageView造成了綁定關係。this

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);
    }
}

固然,咱們須要判斷下ImageView以前是否已經綁定了,若是以前綁定過但與本次的圖片不一樣,那咱們就要按最新的須要重新綁定下,若是以前與如今的一致,則保持原狀,再也不重新綁定,代碼中的cancelPotentialWork就是作這個工做的,其代碼以下:

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;
}

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;
}

最後,咱們在Task的onPostExecute函數中,把加載的圖片更新到視圖中去,在更新前咱們須要檢查下Task是否被取消,而且當前的Task是不是那個與ImageView關聯的Task,一致則咱們把圖片更新到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適配器的getView函數中調用上面的loadBitmap函數就OK了~

下一節咱們來講說緩存,加入緩存讓這個機制更增強大。。

感謝收看! 多多好評,在此謝過!

相關文章
相關標籤/搜索