1. 程式人生 > >Android圖片快取分析與優化

Android圖片快取分析與優化

protected int sizeOf(String key, Drawable value) {
   if(value!=null) {
      if (value instanceof BitmapDrawable) {
         Bitmap bitmap = ((BitmapDrawable) value).getBitmap();
         return bitmap.getRowBytes() * bitmap.getHeight();
      } 
      return 1;
   }else{
      return  0;
   }
}

        然而真正移除物件是在trimToSize(maxSize

)這個方法中:

public void trimToSize(int maxSize) {
    while (true) {
        K key;
        V value;
        synchronized (this) {
            ... ...
            if (size <= maxSize || map.isEmpty()) {
                break;
            }
            Map.Entry toEvict = map.entrySet().iterator().next();
            key = toEvict.getKey();
            value
= toEvict.getValue(); map.remove(key); size -= safeSizeOf(key, value); evictionCount++; } entryRemoved(true, key, value, null); } }

        這裡會檢查當前快取容量,size <= maxSize便移除頭部物件。最後呼叫了entryRemoved(true, key, value, null)方法。

        因此,我們可以重寫該方法來處理淘汰物件:

protected void entryRemoved(boolean evicted, String key, Drawable oldValue, Drawable newValue) {
   if (evicted) {
      if (oldValue != null) {
         //當硬快取滿了,根據LRU規則移入軟快取
         synchronized(mSoftBitmapCache) {
            mSoftBitmapCache.put(key, new SoftReference(oldValue));
         }
      }
   }else{//主動移除,回收無效空間
      recycleDrawable(oldValue);
   }
}

        當evicted變數為true時,屬於為騰出快取空間被呼叫,將被淘汰的物件插入軟引用快取mSoftBitmapCache中。

        當evicted變數為false時,屬於主動淘汰物件,看下面程式碼:

public final V remove(K key) {
    ... ...
    V previous;
    synchronized (this) {
        previous = map.remove(key);
        if (previous != null) {
            size -= safeSizeOf(key, previous);
        }
    }
    if (previous != null) {
        entryRemoved(false, key, previous, null);
    }
    return previous;
}

        entryRemoved方法在LRUCache的remove方法中呼叫時,evicted引數的值為false,因此這裡直接回收圖片物件。 

        如果軟引用快取mSoftBitmapCache超出上限,也根據LRU規則進行淘汰,直接回收物件的記憶體空間。這裡參考LRUCache的實現方式進行初始化:

this.mSoftBitmapCache= new LinkedHashMap>(SOFT_CACHE_SIZE, 0.75f, true){ 
   @Override
   protected boolean removeEldestEntry(Entry> eldest) {
      if (size() > SOFT_CACHE_SIZE) {//快取數量不超過10
         if(eldest!=null){
            SoftReference bitmapReference=eldest.getValue();
            if(bitmapReference!=null){
               Drawable oldValue=bitmapReference.get();
               recycleDrawable(oldValue);
            }
         }
         return true;
      }
      return false;
   }
};

        不同的是重寫了removeEldestEntry方法,這個方法主要用於判斷快取容量是否超過上限,如果超出則回收被淘汰的物件。

        再看看LinkedHashMap類的put方法呼叫了addNewEntry方法,在該方法中會根據removeEldestEntry方法的返回來決定是否移除物件:

public V put(K key, V value) {
        ... ...
    addNewEntry(key, value, hash, index);
    return null;
}
void addNewEntry(K key, V value, int hash, int index) {
    LinkedEntry header = this.header;
    // Remove eldest entry if instructed to do so.
    LinkedEntry eldest = header.nxt;
    if (eldest != header && removeEldestEntry(eldest)) {
       remove(eldest.key);
    }
        ......
}

        因此,當size() > SOFT_CACHE_SIZE時,便對老物件進行移除操作。 從快取中獲取物件的方法:

   public Drawable getBitmap(String url){
      // 先從硬快取中獲取
      Drawable bitmap = get(url);
      if (bitmap != null) {
         return bitmap;
      }
      synchronized (mSoftBitmapCache) {
         SoftReference bitmapReference = mSoftBitmapCache.get(url);
         if (bitmapReference != null) {
            bitmap = bitmapReference.get();
            if (bitmap != null) {
               //移入硬快取
               put(url, bitmap);
               mSoftBitmapCache.remove(url);
               return bitmap;
            } else {
               mSoftBitmapCache.remove(url);
            }
         }
      }
      return null;
   }

        優先從硬快取中拿,如果存在則返回。否則查詢軟引用快取,存在則返回物件並移入硬快取中。

        最後上完整的程式碼:

public class BitmapLRUCache extends LruCache{
   private final int SOFT_CACHE_SIZE = 10; // 軟引用快取容量
   private LinkedHashMap> mSoftBitmapCache;//軟引用快取,已清理的資料可能會再次使用
   public BitmapLRUCache(int maxSize) {
      super(maxSize);
      this.mSoftBitmapCache= new LinkedHashMap>(SOFT_CACHE_SIZE, 0.75f, true){// true 採用LRU排序,移除隊首
         @Override
         protected boolean removeEldestEntry(Entry> eldest) {
            if (size() > SOFT_CACHE_SIZE) {//快取數量不超過10
               if(eldest!=null){
                  SoftReference bitmapReference=eldest.getValue();
                  if(bitmapReference!=null){
                     Drawable oldValue=bitmapReference.get();
                     recycleDrawable(oldValue);
                  }
               }
               return true;
            }
            return false;
         }
      };
   }
   public Drawable getBitmap(String url){
      // 先從硬快取中獲取
      Drawable bitmap = get(url);
      if (bitmap != null) {
         return bitmap;
      }
      synchronized (mSoftBitmapCache) {
         SoftReference bitmapReference = mSoftBitmapCache.get(url);
         if (bitmapReference != null) {
            bitmap = bitmapReference.get();
            if (bitmap != null) {
               //移入硬快取
               put(url, bitmap);
               mSoftBitmapCache.remove(url);
               return bitmap;
            } else {
               mSoftBitmapCache.remove(url);
            }
         }
      }
      return null;
   }
   private  int getSizeInBytes(Bitmap bitmap) {
      int size = bitmap.getRowBytes() * bitmap.getHeight();//每一行畫素點所佔用的位元組數 *  高度
      return size;
   }
   protected int sizeOf(String key, Drawable value) {
      if(value!=null) {
         if (value instanceof BitmapDrawable) {
            Bitmap bitmap = ((BitmapDrawable) value).getBitmap();
            return getSizeInBytes(bitmap);
         }
         return 1;
      }else{
         return  0;
      }
   }
   protected void entryRemoved(boolean evicted, String key, Drawable oldValue, Drawable newValue) {
      super.entryRemoved(evicted, key, oldValue, newValue);
      if (evicted) {
         if (oldValue != null) {
            //當硬快取滿了,根據LRU規則移入軟快取
            synchronized(mSoftBitmapCache) {
               mSoftBitmapCache.put(key, new SoftReference(oldValue));
            }
         }
      }else{//主動移除,回收無效空間
         recycleDrawable(oldValue);
      }
   }
   private void recycleDrawable(Drawable oldValue) {
      if (oldValue != null) {
         try {
            if (oldValue instanceof BitmapDrawable) {
               Bitmap bitmap = ((BitmapDrawable) oldValue).getBitmap();
               bitmap.recycle();
            }
            Log.i("BitmapLRUCache", "oldValue:" + oldValue);
         } catch (Exception exception) {
            Log.i("BitmapLRUCache", "Failed to clear Bitmap images on close", exception);
         } finally {
            oldValue = null;
         }
      }
   }
}
測試         測試機器為華為G525,系統版本為4.1。執行改進後的程式碼,在AndroidStudio中檢視Monitors欄,啟動程式並進行簡單操作,很清楚的看到記憶體佔用的實時變化以及釋放的過程。