在"高效的加載圖片"課程中咱們討論了BitmapFactory.decode方法,可是若是圖片資源是從網絡或者外存(或者其餘非內存的存儲位置)讀取 的,那麼咱們不該該在UI線程中執行加載圖片的操做。由於圖片加載的時間是不可預期的並由衆多的因素所決定(外存儲器或者網絡的讀取速度,圖片的大小,CPU的性能,等等)。若是加載圖片的操做阻塞了UI線程,系統會把您的應用程序標識爲"未響應"並提示用戶關閉它(詳情請參見響應式設計)。 html
這一節將要介紹AsyncTask的使用,並介紹怎樣處理併發問題。 android
AsyncTasks類提供了簡單的方式用來在後臺線程執行一些任務,並把結果傳遞到到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引用的內容不被GC回收。咱們這裏並不能保證當任務結束的時候ImageView的引用仍然可用,因此您也必須在onPostExecute()中檢查引用。舉個例子,當用戶從當前Activity跳轉到其餘Activity或者某個設置在任務結束前發生變化的話,這個ImageView可能會不在存在。 併發
若是要一步的加載一張圖片,只需建立一個AsyncTask對象並調用execute方法。 async
public void loadBitmap(int resId, ImageView imageView) {
BitmapWorkerTask task = new BitmapWorkerTask(imageView);
task.execute(resId);
} ide
一般,當使用像ListView或者GridView這類UI組件同時的使用上一節的AsyncTask加載圖片時會遇到問題。爲了有效的使用內存,這些控件會在滾動時回收子View,若是其中的每個View都觸發了一個AsyncTask,那麼咱們不能保證這些AsyncTask已經完成了,那麼該相關聯的View實際上並無被回收。此外,咱們也不能保證多個AsyncTask結束時的順序會和它們開始時的順序同樣。 性能
"使用多線程提高性能"這篇博客深刻的討論了併發性,而且提出了一個解決方案——ImageView保留一個最近AsyncTask的引用,該AsyncTask在任務完成時能夠被檢查到。使用相同的方法,上一節中的AsyncTask可使用如下相同的模式進行擴展。 ui
建立一個專用的Drawable子類來保存對AsyncTask的引用,既然這樣,一個BitmapDrawable對象被用來當任務結束時在ImageView上顯示一個佔位圖片。 this
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();
}
}
在執行BitampWorkerTask以前,您須要建立一個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);
}
}
示例代碼中使用的cancelPotentailWork方法,用來檢查是否有其餘正在運行的任務關聯了ImageView。若是存在,它將嘗試使用cacel()方法取消上一個任務。在少數狀況下,新的任務數據與已經存在的任務的數據同樣的話,則什麼都不須要發生。如下是cancelPotentialWork方法的實現:
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;
}
}
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中像其餘組件回收它們的子views同樣使用了。當您想要讓一張圖片在ImageView中顯示時,只需簡單的調用loadBitmap便可。舉個例子,若是用來在一個GridView中顯示圖片的話,那麼這個方法能夠在GridView的adapter的getView()方法中調用。