通過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();
}
官網
