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