Android—在UI線程之外處理位元影像

來源:互聯網
上載者:User

本文譯自:http://developer.android.com/training/displaying-bitmaps/process-bitmap.html

如果源圖片來自磁碟或網路(或者其他任何記憶體以外地方),那麼在“高效的載入大位元影像”一文中所討論的BitmapFactory.decode*方法就不應該在主UI線程中執行。載入圖片所需的時間是不可預知的,並且還要依賴各種因素(如磁碟或網路的讀取速度、圖片的尺寸、CPU的處理能力等)。如果這些因素中有一個阻塞了UI線程,那麼系統把你的應用程式標記為非響應程式,並且使用者可以選擇關閉它。

本文討論如何使用AsyncTask在後台線程中處理位元影像,以及如何處理並發問題。

使用AsyncTask

AsyncTask類為在後台線程中執行某些任務提供了比較容易的方法,並且它會把執行結果返回給UI線程。要使用它,就要建立一個子類,並重寫相應的方法。以下是使用AsyncTask類和decodeSampleBitmapFromResource()方法把大位元影像載入到一個ImageView中的例子:

classBitmapWorkerTaskextendsAsyncTask<Integer,Void,Bitmap>{
    private final WeakReference<ImageView> imageViewReference;
    private int data = 0;

    public BitmapWorkerTask(ImageView imageView) {
        // Use aWeakReference to ensure the ImageView can be garbage collected
        imageViewReference = new WeakReference<ImageView>(imageView);
    }

    // Decode image inbackground.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        data = params[0];
        return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
    }

    // Once complete, see ifImageView 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()方法中檢查該引用。例如,如果使用者離開該Activity,或者在任務完成之前,相關的配置發生了變化,那麼ImageView就可能不再存在。

要非同步載入位元影像,只需簡單的建立一個新的任務並執行它:

publicvoidloadBitmap(int
resId,ImageViewimageView){
    BitmapWorkerTask task = new BitmapWorkerTask(imageView);
    task.execute(resId);
}

處理並發

通常當諸如ListView和GridView這樣的View組件跟AsyncTask結合使用時,就會引入另外的問題。為了提高記憶體的使用效率,這些組件會在使用者滾動時會回收子View。如果每個子View都觸發一個AsyncTask任務,那麼不能夠保證AsyncTask任務被執行完成之前,相關的子View不被回收。此外,也不能夠保證非同步任務按照啟動順序來完成。

建立一個專用的Drawable子類來儲存工作任務的引用。在本文中使用BitmapDrawable,以便在任務執行完成時,其對應的圖片能夠被顯示在ImageView中。

staticclassAsyncDrawableextendsBitmapDrawable{
    private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;

    public AsyncDrawable(Resources res, Bitmap bitmap,
            BitmapWorkerTask bitmapWorkerTask) {
        super(res, bitmap);
        bitmapWorkerTaskReference =
            new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
    }

    public BitmapWorkerTaskgetBitmapWorkerTask() {
        return bitmapWorkerTaskReference.get();
    }
}

在執行BitmapWorkerTask之前,你要建立一個AsyncDrawable對象,並把它跟目標的ImageView對象綁定:

publicvoidloadBitmap(int
resId,ImageViewimageView){
    if (cancelPotentialWork(resId, imageView)) {
        final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
        final AsyncDrawableasyncDrawable =
                new AsyncDrawable(getResources(), mPlaceHolderBitmap,task);
        imageView.setImageDrawable(asyncDrawable);
        task.execute(resId);
    }
}

上例代碼中的cancelPotentialWork方法會檢查其他正在啟動並執行任務是否已經跟該ImageView對象相關聯。如果關聯了,它會嘗試調用cancel()方法來取消之前的任務。在少數場合,新任務的資料會跟既存的任務相匹配,並且不會再有其他的需求發生。以下是cancelPotentialWork方法的實現:

publicstaticbooleancancelPotentialWork(int
data,ImageViewimageView){
    final BitmapWorkerTaskbitmapWorkerTask =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 withthe ImageView, or an existing task was cancelled
    return true;
}

一個輔助的方法:getBitmapWorkerTask()被用於擷取與上述任務相關聯的ImageView對象:

privatestaticBitmapWorkerTaskgetBitmapWorkerTask(ImageViewimageView){
   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對象:

classBitmapWorkerTaskextendsAsyncTask<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組件,以及其他任何回收子View的組件。簡單的調用loadBitmap方法,就可以把一個圖片設定給ImageView對象。例如,在GridView中會在適配器支援的getView方法中實現這種模式。

 

範例程式碼:http://download.csdn.net/detail/fireofstar/4874551

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.