原文鏈接 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
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
處理併發問題
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的資源