Android批量圖片加載經典系列——使用LruCache、AsyncTask緩存並異步加載圖片


一、問題描述

使用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群:JRedu技術交流 (452379712)

 

作者: 傑瑞教育
出處: http://www.cnblogs.com/jerehedu/ 
本文版權歸煙台傑瑞教育科技有限公司和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM