一、問題描述 |
使用LruCache、AsyncTask實現批量圖片的加載並達到下列技術要求
1、從緩存中讀取圖片,若不在緩存中,則開啟異步線程(AsyncTask)加載圖片,並放入緩存中
2、及時移除無效的異步線程;保證異步加載圖片時不會亂序
3、只對當前屏幕可見部分進行緩存、異步加載圖片
4、優化性能杜絕OOM
二、案例介紹 |
案例實現照片牆效果
三、主要技術 |
LruCache
內存緩存技術,在Android中 專門用來做圖片緩存處理的組件,主要使用步驟
(1) 設置緩存圖片的內存大小,如設置為手機內存的1/8(當緩存的圖片達到了預先設定的值的時候,那么近期使用次數最少的圖片就會被回收掉)代碼如下:
// 獲取應用程序最大可用內存 int maxMemory = (int) Runtime.getRuntime().maxMemory(); int cacheSize = maxMemory / 8; // 設置圖片緩存大小為程序最大可用內存的1/8 mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { return bitmap.getByteCount(); } };
(2) 將圖片放入緩存中(LruCache里面的鍵值對通常分別是URL和對應的圖片)
mMemoryCache.put(key, bitmap);
(3) 從緩存中取圖片
mMemoryCache.get(key);
AsyncTask
進行耗時操作比如加載圖片要求不要阻塞UI線程,就必須使用異步任務。AsyncTask是不需要借助thread+handler即可實現異步任務的組件,使用起來比較簡單且更輕量級一些。實現AsyncTask步驟如下:
(1) 擴展子AsyncTask,如
class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap>
(2) 重寫AsyncTask中的幾個方法
onPreExecute(), 該方法將在執行實際的后台操作前被UI線程調用。可以在該方法中做一些准備工作,如在界面上顯示一個進度條。
doInBackground(Params...), 將在onPreExecute 方法執行后、執行,該方法運行在后台線程中,主要負責執行那些很耗時的后台計算工作,如加載圖片
onPostExecute(Result), 在doInBackground 執行完成后,該將被UI線程調用,后台的計算結果將通過該方法傳遞到UI線程
(3) 在UI線程中通過調用AsyncTask的execute(Params…);
開始執行異步任務並向后台任務傳入數據,執行次序 onPreExecute()——> doInBackground(Params...)——> onPostExecute(Result)
經驗:對大量圖片加載,要求每個圖片都要在一個后台任務中取運行,因此需要使用集合記錄所有正在下載或等待下載的任務
Set<BitmapWorkerTask> taskCollection=new HashSet< BitmapWorkerTask >();
當緩存中沒有圖片,添加任務並啟動異步處理,片段代碼如:
if (bitmap == null) {//如果緩存沒有 BitmapWorkerTask task = new BitmapWorkerTask(); taskCollection.add(task); task.execute(imageUrl);//執行異步任務,並傳入加載的圖片url地址 }
要注意及時移除已完成的任務,如下代碼
protected void onPostExecute(Bitmap bitmap) { …. taskCollection.remove(this); }
並且在適配器控件滑動停止時取消正在執行的任務,片段代碼如下:
public void onScrollStateChanged(AbsListView view, int scrollState) { // 僅當GridView靜止時才去下載圖片,GridView滑動時取消所有正在下載的任務 if (scrollState == SCROLL_STATE_IDLE) { loadBitmaps(mFirstVisibleItem, mVisibleItemCount); } else { cancelAllTasks();//取消所有正在下載或等待下載的任務。 } } public void cancelAllTasks() { if (taskCollection != null) { for (BitmapWorkerTask task : taskCollection) { task.cancel(false); } }}
三、完整代碼 |
1、 MainActivity
public class MainActivity extends Activity { private GridView mPhotoWall; private PhotoWallAdapter adapter; private ArrayList<File> list; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); list = new ArrayList<File>(); getAllFiles(new File("/sdcard")); mPhotoWall = (GridView) findViewById(R.id.photo_wall); adapter = new PhotoWallAdapter(this, 0, list, mPhotoWall); mPhotoWall.setAdapter(adapter); } /** * 獲得指定目錄下圖片文件 */ private void getAllFiles(File root) { File files[] = root.listFiles(); if (files != null) for (File f : files) { if (f.isDirectory()) { getAllFiles(f); } else { if (f.getName().indexOf(".png") > 0 || f.getName().indexOf(".jpg") > 0 || f.getName().indexOf(".jpeg") > 0) this.list.add(f); } } } protected void onDestroy() { super.onDestroy(); adapter.cancelAllTasks();// 退出程序時結束所有的下載任務 } }
2、PhotoWallAdapter適配器
public class PhotoWallAdapter extends ArrayAdapter<File> implements OnScrollListener { //記錄所有正在下載或等待下載的任務。 private Set<BitmapWorkerTask> taskCollection; // 圖片緩存技術的核心類,用於緩存所有下載好的圖片,在程序內存達到設定值時會//將最少最近使用的圖片移除掉。 private LruCache<String, Bitmap> mMemoryCache; //GridView的實例 private GridView mPhotoWall; //第一張可見圖片的下標 private int mFirstVisibleItem; // 一屏有多少張圖片可見 private int mVisibleItemCount; // 記錄是否剛打開程序,用於解決進入程序不滾動屏幕,不會下載圖片的問題。 private boolean isFirstEnter = true; ArrayList<File> list = null; public PhotoWallAdapter(Context context, int textViewResourceId, ArrayList<File> objects, GridView photoWall) { super(context, textViewResourceId, objects); mPhotoWall = photoWall; list = objects; taskCollection = new HashSet<BitmapWorkerTask>(); // 獲取應用程序最大可用內存 int maxMemory = (int) Runtime.getRuntime().maxMemory(); int cacheSize = maxMemory / 8; // 設置圖片緩存大小為程序最大可用內存的1/8 mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { return bitmap.getByteCount(); } }; mPhotoWall.setOnScrollListener(this); } public View getView(int position, View convertView, ViewGroup parent) { final File url = getItem(position); View view; if (convertView == null) { view = LayoutInflater.from(getContext()).inflate( R.layout.photo_layout, null); } else { view = convertView; } final ImageView photo = (ImageView) view.findViewById(R.id.photo); // 給ImageView設置一個Tag,保證異步加載圖片時不會亂序 photo.setTag(url.getAbsolutePath()); setImageView(url.getAbsolutePath(), photo); return view; } /** * 給ImageView設置圖片。首先從LruCache中取出圖片的緩存,設置到ImageView上。如果LruCache中沒有該圖片的緩存, * 就給ImageView設置一張默認圖片。 * @param imageUrl * 圖片的URL地址,用於作為LruCache的鍵。 * @param imageView * 用於顯示圖片的控件。 */ private void setImageView(String imageUrl, ImageView imageView) { Bitmap bitmap = getBitmapFromMemoryCache(imageUrl); if (bitmap != null) { imageView.setImageBitmap(bitmap); } else { bitmap=getLoacalBitmap(imageUrl); imageView.setImageResource(R.drawable.empty_photo); } } /** * 將一張圖片存儲到LruCache中。 * @param key * LruCache的鍵,這里傳入圖片的URL地址。 * @param bitmap * LruCache的鍵,這里傳入從網絡上下載的Bitmap對象。 */ @SuppressLint("NewApi") public void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (getBitmapFromMemoryCache(key) == null) { mMemoryCache.put(key, bitmap); } } /** * 從LruCache中獲取一張圖片,如果不存在就返回null。 * @param key * LruCache的鍵,這里傳入圖片的URL地址。 * @return 對應傳入鍵的Bitmap對象,或者null。 */ @SuppressLint("NewApi") public Bitmap getBitmapFromMemoryCache(String key) { return mMemoryCache.get(key); } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { // 僅當GridView靜止時才去下載圖片,GridView滑動時取消所有正在下載的任務 if (scrollState == SCROLL_STATE_IDLE) { loadBitmaps(mFirstVisibleItem, mVisibleItemCount); } else { cancelAllTasks(); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { mFirstVisibleItem = firstVisibleItem; mVisibleItemCount = visibleItemCount; // 下載的任務應該由onScrollStateChanged里調用,但首次進入程序時onScrollStateChanged並不會調用, // 因此在這里為首次進入程序開啟下載任務。 if (isFirstEnter && visibleItemCount > 0) { loadBitmaps(firstVisibleItem, visibleItemCount); isFirstEnter = false; } } /** * 加載Bitmap對象。此方法會在LruCache中檢查所有屏幕中可見的ImageView的Bitmap對象, * 如果發現任何一個ImageView的Bitmap對象不在緩存中,就會開啟異步線程去下載圖片。 * * @param firstVisibleItem * 第一個可見的ImageView的下標 * @param visibleItemCount * 屏幕中總共可見的元素數 */ private void loadBitmaps(int firstVisibleItem, int visibleItemCount) { try { for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) { String imageUrl = list.get(i).getAbsolutePath(); Bitmap bitmap = getBitmapFromMemoryCache(imageUrl); if (bitmap == null) {//如果緩存沒有 BitmapWorkerTask task = new BitmapWorkerTask(); taskCollection.add(task); task.execute(imageUrl);//執行異步任務,並傳入加載的圖片url地址(這里是sd卡上的圖片) } else { ImageView imageView = (ImageView) mPhotoWall .findViewWithTag(imageUrl); if (imageView != null && bitmap != null) { imageView.setImageBitmap(bitmap); } } } } catch (Exception e) { e.printStackTrace(); } } /** * 取消所有正在下載或等待下載的任務。 */ public void cancelAllTasks() { if (taskCollection != null) { for (BitmapWorkerTask task : taskCollection) { task.cancel(false); } } } /** * 異步下載圖片的任務。 */ class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> { /** * 圖片的URL地址 */ private String imageUrl; @Override protected Bitmap doInBackground(String... params) { imageUrl = params[0]; // 在后台開始下載圖片 Bitmap bitmap = getLoacalBitmap(params[0]); if (bitmap != null) { // 圖片下載完成后緩存到LrcCache中 addBitmapToMemoryCache(params[0], bitmap); } return bitmap; } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); // 根據Tag找到相應的ImageView控件,將下載好的圖片顯示出來。 ImageView imageView = (ImageView) mPhotoWall .findViewWithTag(imageUrl); if (imageView != null && bitmap != null) { imageView.setImageBitmap(bitmap); } taskCollection.remove(this); } } private Bitmap getLoacalBitmap(String url) { try { FileInputStream fis = new FileInputStream(url); return BitmapFactory.decodeStream(fis); // /把流轉化為Bitmap圖片 } catch (FileNotFoundException e) { e.printStackTrace(); return null; } } }
想要了解更多內容的小伙伴,可以點擊查看源碼,親自運行測試。
疑問咨詢或技術交流,請加入官方QQ群: (452379712)
出處: http://www.cnblogs.com/jerehedu/
本文版權歸煙台傑瑞教育科技有限公司和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。