本文譯自: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