Displaying Bitmaps Efficiently (三)

在UI線程之外處理圖片

——Android官網原文翻譯

在"高效的加載圖片"課程中咱們討論了BitmapFactory.decode方法,可是若是圖片資源是從網絡或者外存(或者其餘非內存的存儲位置)讀取 的,那麼咱們不該該在UI線程中執行加載圖片的操做。由於圖片加載的時間是不可預期的並由衆多的因素所決定(外存儲器或者網絡的讀取速度,圖片的大小,CPU的性能,等等)。若是加載圖片的操做阻塞了UI線程,系統會把您的應用程序標識爲"未響應"並提示用戶關閉它(詳情請參見響應式設計)。 html

這一節將要介紹AsyncTask的使用,並介紹怎樣處理併發問題。 android

1.使用AsyncTask

AsyncTasks類提供了簡單的方式用來在後臺線程執行一些任務,並把結果傳遞到到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);
            }
        }
    }
}
多線程

指向一個ImageView的WeakReference表示代碼中的AsyncTask並不保證ImageView以及任何ImageView引用的內容不被GC回收。咱們這裏並不能保證當任務結束的時候ImageView的引用仍然可用,因此您也必須在onPostExecute()中檢查引用。舉個例子,當用戶從當前Activity跳轉到其餘Activity或者某個設置在任務結束前發生變化的話,這個ImageView可能會不在存在。 併發

若是要一步的加載一張圖片,只需建立一個AsyncTask對象並調用execute方法。 async

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

2處理併發

一般,當使用像ListView或者GridView這類UI組件同時的使用上一節的AsyncTask加載圖片時會遇到問題。爲了有效的使用內存,這些控件會在滾動時回收子View,若是其中的每個View都觸發了一個AsyncTask,那麼咱們不能保證這些AsyncTask已經完成了,那麼該相關聯的View實際上並無被回收。此外,咱們也不能保證多個AsyncTask結束時的順序會和它們開始時的順序同樣。 性能

"使用多線程提高性能"這篇博客深刻的討論了併發性,而且提出了一個解決方案——ImageView保留一個最近AsyncTask的引用,該AsyncTask在任務完成時能夠被檢查到。使用相同的方法,上一節中的AsyncTask可使用如下相同的模式進行擴展。 ui

建立一個專用的Drawable子類來保存對AsyncTask的引用,既然這樣,一個BitmapDrawable對象被用來當任務結束時在ImageView上顯示一個佔位圖片。 this

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

在執行BitampWorkerTask以前,您須要建立一個AsyncDrawable對象並將其綁定到目標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);
    }
}

示例代碼中使用的cancelPotentailWork方法,用來檢查是否有其餘正在運行的任務關聯了ImageView。若是存在,它將嘗試使用cacel()方法取消上一個任務。在少數狀況下,新的任務數據與已經存在的任務的數據同樣的話,則什麼都不須要發生。如下是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;
        }
    }
    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;
}

最後一步是在BitmapWorkerTask中執行更新onPostExecute(),這將檢查任務是否被取消,以及當前任務是否與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中像其餘組件回收它們的子views同樣使用了。當您想要讓一張圖片在ImageView中顯示時,只需簡單的調用loadBitmap便可。舉個例子,若是用來在一個GridView中顯示圖片的話,那麼這個方法能夠在GridView的adapter的getView()方法中調用。

相關文章
相關標籤/搜索