Bitmap 處理之不要在UI主線程中處理Bitmap

原文鏈接 http://developer.android.com/intl/zh-CN/training/displaying-bitmaps/process-bitmap.html

在使用BitmapFactory.decode*解析圖片時,最好不要在UI主線程中處理,因爲圖片的來源是未知的,有可能是從硬盤讀取的,也有可能是是網絡的圖片資源,這時在解析圖片時,會有一些不可控的因素,如(網速較慢等),如果在UI主線中處理,就會有可能block主線程,從而導致應用無相應(ANR),會造成很不好的用戶體驗。Android本身提供很多的方法,在非UI線程中處理一些比較耗時的操作,如Handler,AsyncTask等,下面是使用AsyncTask的一種方法。

使用AsyncTask

AsyncTask提供了一種很簡單的方式,在後臺線程處理複雜的操作,處理完成之後,會將處理之後的結果返回到UI主線程,具體的實現入下:

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<ImageView> imageViewReference 使用一個軟引用,保證ImageView可以被回收, 主要的耗時的操作在doInBackground(Integer... params) 方法中實施,處理完成之後的返回的結果在onPostExecute方法中使用,onPostExecute是在主線程運行的。 方法decodeSampledBitmapFromResource 的具體實現請參考 “Bitmap處理之流暢的加載大的Bitmap”
實際的應用:

 public void loadBitmap(int resId, ImageView imageView) {

   BitmapWorkerTask task = new BitmapWorkerTask(imageView);

   task.execute(resId);

 }

兩個參數分別是 resId是通過R.drawable 獲得的資源圖片,imageView是 要顯示的圖片的ImageView

處理併發問題

對於普通的View如ListViwe和GridView在聯合上述的AsyncTask加載資源時,會導致其他的問題。爲了更好的利用內存資源,這些Viwe在滑動時,會對子View做資源回收,如果子View使用AsyncTask加載資源時,就不能保證子View能夠及時的回收資源,也就是說開始一步加載的順序和完成的順序會不一致。 關於多線程處理的問題可以參考 “Multithreading for Performance ”的一篇Blog 可以創建一個專有的BitmapDrawable,在AsyncTask加載圖片完成之前,可以使用一個empty(預覽圖)bitmap來顯示

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 執行之前,可以先使用一個預覽圖顯示(或者一張空的圖片)

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

   }

 }

mPlaceHolderBitmap可以是一個empty_photo.png的圖片
cancelPotentialWork 方法是檢查是否有另外一個running task和ImageView綁定,如果是,則前一個task會執行cancel()

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;

}

如果返回false,說明已經有一個工作者線程在運行 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類

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

           }

       }

   }

}

加入了判斷條件,判斷當前的線程是否已經被cancel,ImageView的線程是否和當前的線程是否是同一個線程


應用場景
如在ListView或者GridView的getView()方法中,可以點用loadBitmap()方法來加載子view的資源
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章