[原]Android官方圖片載入利器BitmapFun解析

雨知發表於2013-12-07

通過BitmapFun在專案中使用,結合程式碼瞭解一下BitmapFun載入圖片的原理,以及最佳使用實踐。本文說明不包括BitmapFun的快取部分。

Android開發在使用ListView和GridView時,可能會有很多網路圖片需要載入,通常我們會為每個圖片載入啟動一個Thread或者直接使用官方提供的AsyncTask,來做Http非同步載入,但當每個ImageView子檢視都觸發一個AsyncTask來非同步載入圖片時,這樣就會產生如下問題:

1. 當使用者快速滑動時,ImageView已經被回收,而繫結的執行緒還在執行,浪費CPU,浪費記憶體。

2. 無法確保當前檢視在結束時,分配的檢視已經進入迴圈佇列中給另外一個子檢視進行重用,意思就是,圖片顯示錯位了,不該顯示到當前問題的圖片卻顯示了,這個是經常遇到的問題。可以結合Adapter中的getView方法的convertView引數理解,ListView是回收和重複利用item的。

3. 無法確保所有的非同步任務能夠按順序執行。

在這些問題下,官網給出的答案是,使用下面的方法來保證:

1. ImageView和Task繫結準確的載入對應圖片;

2. ImageView和Task無法對應時則取消任務;

BitmapFun實現多執行緒併發載入圖片的原理:

一個類,兩個方法:

class AsyncDrawable extends BitmapDrawable{...}

boolean cancelPotentialWork(String url, ImageView imageView){...}

ImageViewResizeTask getBitmapWorkerTask(ImageView imageView){...}

具體程式碼實現如下:

/**
 * 擴充套件BitmapDrawable類,通過弱引用關聯任務BitmapWorkerTask
 * BitmapDrawable被用來作為佔點陣圖片,繫結任務到ImageView中
 */
private class AsyncDrawable extends BitmapDrawable {
    private final WeakReference<BitmapWorkerTask> viewResizeTaskReference;

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

    public BitmapWorkerTask getBitmapWorkerTask() {
        return viewResizeTaskReference.get();
    }
}

/**
 * 確保ImageView執行的是它對應的Task,否則取消任務 
 * @param url
 * @param imageView
 * @return
 */
private boolean cancelPotentialWork(String url, ImageView imageView) {  
	// 獲得ImageView對應的Task
    final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);  
  
    if (bitmapWorkerTask != null) {  
        final String imgUrl = bitmapWorkerTask.url;  
        if (imgUrl == null || !imgUrl.equals(url)) {  
            // 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;  
}  

/**
 * 獲得已經被分配到ImageView的指定的Task 
 * @param imageView
 * @return
 */
private 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;
}

使用方法:

/**
 * 非同步載入圖片
 * @param url
 * @param imageView
 */
private void loadBitmap(String url, ImageView imageView) {  
    if (cancelPotentialWork(url, imageView)) {  
        final BitmapWorkerTask task = new BitmapWorkerTask(imageView);  
        final AsyncDrawable asyncDrawable = new AsyncDrawable(context.getResources(), null, task);  
        imageView.setImageDrawable(asyncDrawable);  
        task.execute(url);  
    }  
}  

/*
 * 非同步載入圖片Task類
 */
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);
            }
        }
    }
}

 

最佳使用實踐,提高流暢度

1. 設定ListView的OnScrollListener事件,在滑動的時候不載入圖片

public void onScrollStateChanged(AbsListView view, int scrollState) {
	if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
		if (!Utils.hasHoneycomb()) {
			mImageFetcher.setPauseWork(true);
		}
	} else {
		mImageFetcher.setPauseWork(false);
	}
}

2. 在Activity或Fragment的onResume(),onPause(),onDestroy()方法中呼叫恰當方法非常有用

@Override
public void onResume() {
	super.onResume();
	mImageFetcher.setExitTasksEarly(false);
}

@Override
public void onPause() {
	super.onPause();
	mImageFetcher.setPauseWork(false);
	mImageFetcher.setExitTasksEarly(true);
	mImageFetcher.flushCache();
}

@Override
public void onDestroy() {
	super.onDestroy();
	mImageFetcher.closeCache();
}

 

官網

Processing Bitmaps Off the UI Thread   

相關文章