【Google官方教程】第二課:在非UI線程處理Bitmap

聲明:Ryan的博客文章歡迎您的轉載,但在轉載的同時,請註明文章的來源出處,不勝感激! :-)  html

http://my.oschina.net/ryanhoo/blog/88344
java

譯者:Ryan Hoo android

來源:https://developer.android.com/develop/index.html
網絡

譯者按: 在Google最新的文檔中,提供了一系列含金量至關高的教程。由於種種緣由而不爲人知,真是惋惜!Ryan將會細心整理,將之翻譯成中文,但願對開發者有所幫助。 併發

        本系列是Google關於展現大Bitmap(位圖)的官方演示,能夠有效的解決內存限制,更加有效的加載並顯示圖片,同時避免讓人頭疼的OOM(Out Of Memory)。
異步

------------------------------------------------------------------------------------- async

譯文: ide

       在高效地加載Bitmap中,咱們討論了BitmapFactory.decode*系列方法,若是源數據來自硬盤或者網絡(或者除內存以外的來源),是不該該在主UI線程執行的。這是由於讀取這樣的數據所需的加載時間是不肯定的,它依賴於多種因素(從硬盤或網絡的讀取速度、圖片的大小、CPU的功率等等)。若是這些任務裏面任何一個阻塞了UI線程,系統會將你的應用標記爲未響應,而且用戶能夠選擇關閉應用(更多信息,請參閱Designing for Responsiveness)。 this


        這節課將教會你使用AsyncTask在後臺線程處理Bitmap並向你展現如何處理併發問題。 spa

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

        ImageView的WeakReference(弱引用)能夠確保AsyncTask不會阻止ImageView和它的任何引用被垃圾回收器回收。不能保證在異步任務完成後ImageView依然存在,所以你必須在onPostExecute()方法中檢查引用。ImageView可能已經不存在了,好比說,用戶在任務完成前退出了當前Activity或者應用配置發生了變化(橫屏)。

        爲了異步加載Bitmap,咱們建立一個簡單的異步任務而且執行它:

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


處理併發

        常見的View(視圖)組件如ListView和GridView在於AsyncTask配合使用的時候引出了另一個問題,這個咱們在上一節中提到過。爲了提高內存效率,當用戶滾動這些組件的時候進行子視圖的回收(主要是回收不可見的視圖)。若是每一個子視圖都觸發了一個AsyncTask,沒法保證在任務完成的時候,關聯視圖尚未被回收而被用來顯示另外一個子視圖。此外,也沒法保證異步任務結束的循序與它們開始的順序一致。

        Multithreading for Performance這篇文章深刻討論瞭如何處理併發問題,而且給出瞭如何在任務結束的時候檢測ImageView存儲最近使用的AsyncTask引用的解決方案。使用類似的方法,能夠遵循相似的模式來擴展前面的AsyncTask。

        建立一個專用的Drawable之類,用來存儲worker task的引用。在這種狀況下,任務結束的時候BitmapDrawable能夠取代圖像佔位符顯示在ImageView中。

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:
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);
    }
}
        在上面的代碼示例中引用的cancelPotentialWork方法能夠檢測一個執行中的任務是否與ImageView有關聯。若是有關聯,它將經過調用canceel()方法試圖取消以前的任務。在少數狀況下,新的任務中的數據與現有的任務相匹配,所以不須要作什麼。下面是calcelPotentialWork的具體實現:
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;
}
        最後一步是更新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以及其餘任何須要回收子視圖的組件中。當你只須要爲ImageView設置圖片,調用loadBitmap就能夠了。例如,在GridView中實現的方式是在Adapter的getView()方法中。
相關文章
相關標籤/搜索