Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android官方開發文檔Training系列課程中文版(高效顯示位圖之在非UI線程中處理圖片)

Android官方開發文檔Training系列課程中文版(高效顯示位圖之在非UI線程中處理圖片)

編輯:關於Android編程

原文地址:http://android.xsoftlab.net/training/displaying-bitmaps/process-bitmap.html

我們在上節課Load Large Bitmaps Efficiently中討論了BitmapFactory.decode*方法,說到了不應該在UI線程中執行讀取數據的過程,尤其是從磁盤或者網絡上讀取數據(或者其它讀取速度次於內存的地方)。讀取數據的時間是不可預料的,這取決於各種各樣的因素(從磁盤或者網絡讀取的速度、圖片的大小、CPU的功率,etc.)。如果這其中的一個因素阻塞了UI線程,那麼系統會標志程序為無響應標志,並會給用戶提供一個關閉的選項(請查看Designing for Responsiveness獲取更多信息)。

這節課討論了通過使用AsyncTask在非UI線程中處理位圖以及展示如何處理並發問題。

使用AsyncTask

類AsyncTask提供了一種簡要的方式來處理後台進程的工作,並會將處理後的結果推送到UI線程中。如果要使用這個類,需要創建該類的子類,然後重寫所提供的方法。這裡有個例子,展示了如何使用AsyncTask及decodeSampledBitmapFromResource()來加載一張大圖到ImageView上:

class BitmapWorkerTask extends AsyncTask {
    private final WeakReference imageViewReference;
    private int data = 0;
    public BitmapWorkerTask(ImageView imageView) {
        // Use a WeakReference to ensure the ImageView can be garbage collected
        imageViewReference = new WeakReference(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或者在任務結束的時候一些配置發生了變化。

為了啟動異步任務來加載圖片,需要簡單的創建一個新任務並執行它:

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

處理並發

一些普通的View控件比如ListView和GridView會涉及到另一個問題,就是當與AsyncTask結合使用的時候會出現並發問題。為了能有效的使用內存,這些控件會隨著用戶的滑動來回收子View。如果每一個子View都會觸發一個AsyncTask,那麼就不能保障在任務完成的時候,與之相關聯的View沒有被回收利用。此外,對於順序啟動的任務也不能保障可以按順序完成。

博客Multithreading for Performance進一步的討論了如何處理並發,它提供了一個解決方案:在ImageView中存儲了最近的AsyncTask的引用,這個引用可以在任務完成的時候對最近的AsyncTask進行檢查。通過類似的辦法,那麼上面章節的AsyncTask可以被擴展成類似的模式。

創建一個專用的Drawable子類來存儲工作任務的引用。在這種情況下,BitmapDrawable就會被用到,所以在任務完成之前可以有一個占位圖顯示在ImageView上:

static class AsyncDrawable extends BitmapDrawable {
    private final WeakReference bitmapWorkerTaskReference;
    public AsyncDrawable(Resources res, Bitmap bitmap,
            BitmapWorkerTask bitmapWorkerTask) {
        super(res, bitmap);
        bitmapWorkerTaskReference =
            new WeakReference(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關聯上了。如果是這樣的話,它會通過cancel()嘗試取消原來的任務。在少數情況下,新建的任務數據可能會與已經存在的任務相匹配,所以就不要有進一步的動作。下面是cancelPotentialWork()方法的實現:

public static boolean cancelPotentialWork(int data, ImageView imageView) {
    final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
    if (bitmapWorkerTask != null) {
        final int bitmapData = bitmapWorkerTask.data;
        // If bitmapData is not yet set or it differs from the new data
        if (bitmapData == 0 || 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 {
    ...
    @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這種會回收它們子View的組件上了,簡單的調用loadBitmap()就可以正常給ImageView設置圖片了。比如,在一個GridView的實現中,這個方法就可以在相應適配器的getView()方法中使用。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved