[學習筆記]Android開發藝術探索:Bitmap的加載和Cache

Bitmap的高效加載

BitmapFactory類提供四種方法:decodeFile、decodeResource、decodeStream和decodeByteArray;其中decodeFile和decodeResource間接的調用了decodeStream方法;這四個方法最終在Android底層實現。

如何高效的加載Bitmap?核心思想:按需加載;很多時候ImageView並沒有原始圖片那麼大,所以沒必要加載原始大小的圖片。採用BitmapFactory.Options來加載所需尺寸的圖片。 通過BitmapFactory.Options來縮放圖片,主要是用到了它的inSampleSize參數,即採樣率。 inSampleSize應該爲2的指數,如果不是系統會向下取整並選擇一個最接近2的指數來代替;縮放比例爲1/(inSampleSize的二次方)。

Bitmap內存佔用:拿一張1024 * 1024像素的圖片來說,假定採用ARGB8888格式存儲,那麼它佔用的內存爲1024 * 1024 * 4,即4MB。

通過採樣率高效地加載圖片,代碼示例:

 public static Bitmap decodeBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        //1. 將BitmapFactory.Options的inJustDecodeBounds參數設置爲true並加載圖片。
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);

        //2. 根據採樣率的規則並結合目標View的所需大小計算出採樣率inSampleSize。
        options.inSampleSize = calcuateInSampleSize(options, reqWidth, reqHeight);

        //3. 將BitmapFactory.Options的inJustDecodeBounds參數設置爲false,然後重新加載圖片。
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }
     //獲取採樣率
    private static int calcuateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        int width = options.outWidth;
        int height = options.outHeight;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {
            int halfHeight = height / 2;
            int halfWidth = width / 2;
            while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }
// 顯示圖片
Bitmap bitmap = DecodeBitmap.decodeBitmapFromResource(getResources(), R.mipmap.haimei2, 400, 400);
imageView.setImageBitmap(bitmap);

當inJustDecodeBounds參數爲true時,BitmapFactory只會解析圖片的原始寬/高信息,並不會真正的加載圖片。需要注意這時候BitmapFactory獲取的圖片寬/高信息和圖片的位置與程序運行的設備有關。

Android的緩存策略

如何減少流量消耗?緩存。當程序第一次從網絡上加載圖片後,將其緩存在存儲設備中,下次使用這張圖片的時候就不用再從網絡從獲取了。一般情況會把圖片存一份到內存中,一份到存儲設備中,如果內存中沒找到就去存儲設備中找,還沒有找到就從網絡上下載。

目前常用的緩存算法是LRU,是近期最少使用算法,當緩存滿時,優先淘汰那些近期最少使用的緩存對象。採用LRU算法的緩存有兩種:LRUCache(內存緩存)和DiskLruCache(存儲緩存)。

LruCache是Android3.1所提供的一個緩存類,通過support-v4兼容包可以兼容到早期的Android版本。LruCache是一個泛型類,是線程安全的,內部採用LinkedHashMap以強引用的方式存儲外界緩存對象,並提供get和put方法來完成緩存的獲取和添加操作,當緩存滿時,LruCache會移除較早的使用的緩存對象。LruCache初始化時需重寫sizeOf方法,用於計算緩存對象的大小。

  • 強引用:直接的對象引用
  • 軟引用:當一個對象只有軟引用存在的時候,系統內存不足的時此對象會被GC回收。
  • 弱引用 : 當一個對象只有弱引用存在的時候,此對象隨時會被GC回收。

DiskLruCache用於實現磁盤緩存,DiskLruCache得到了Android官方文檔推薦,但它不屬於Android SDK的一部分

  1. DiskLruCache不能通過構造方法來創建,提供了 open() 方法來創建自身。
  2. 通過 edit(String key) 方法來獲取Editor對象
  3. 通過Editor的 newOutputStream(int cacheIndex) 方法打開一個文件輸出流,然後通過這個流將下載的圖片寫入到文件系統中。
  4. 調用Editor的 commit() 來提交寫入操作。如果圖片下載過程中發生異常,通過Editor 的 abort() 來回退整個操作。

DiskLruCache的緩存查找:

  1. 將圖片url轉換爲key,通過 get(String key) 方法得到一個Snapshot對象。
  2. 通過Snapshort對象即可得到緩存的文件輸入流
  3. 從輸入流中得到Bitmap對象
  4. 通過 BitmapFactory.Options 對象來加載一張縮放後的圖片,對FileInputStream的縮放存在問題,因爲FileInputStream是一種有序的文件流,而兩次 decodeStream 調用影響了文 件流的位置屬相,導致第二次 decodeStream 時得到的是null。所以一般通過文件流來得到 對應的文件描述符,通過 BitmapFactory.decodeFileDescriptor() 來加載一張縮放後的圖片。

自己實現一個ImageLoader,包含

  1. 圖片壓縮功能
  2. 內存緩存和磁盤緩存
  3. 同步加載和異步加載的接口設計

ImageLoader的使用

優化列表卡頓現象

  1. 不要在getView中執行耗時操作,不要在getView中直接加載圖片。
  2. 控制異步任務的執行頻率:如果用戶刻意頻繁上下滑動,getView方法會不停調用,從而產生大量的異步任務。可以考慮在列表滑動停止加載圖片;給ListView或者GridView設置setOnScrollListener並在OnScrollListener的onScrollStateChanged方法中判斷列表是否處於滑動狀態,如果是的話就停止加載圖片。
  3. 大部分情況下,可以使用硬件加速解決莫名卡頓問題,通過設置android:hardwareAccelerated="true"即可爲Activity開啓硬件加速。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章