情景學習Android中的LruCache

LruCache類是基於LRU算法的內存緩存管理類,在v4包下有一個,Android3.1及以上在android.util包下也有一個,注意兩個的移除策略是不一樣的。

###一、簡單使用

public class BitmapMemoryCache {

	private static final int maxSize = (int) (Runtime.getRuntime().maxMemory() / 6);
	
	private static final LruCache<String, Bitmap> sBitmapCache = new android.support.v4.util.LruCache<String, Bitmap>(maxSize){
		
		protected int sizeOf(String key, Bitmap value) {
			return sizeOfBitmap(value);
		}
	};
    
    public Bitmap put(String key, Bitmap bitmap) {
    	if(TextUtils.isEmpty(key) || bitmap == null) {
    		return null;
    	}
    	return sBitmapCache.put(key, bitmap);
    }
    
    public Bitmap get(String key) {
    	if(TextUtils.isEmpty(key)) {
    		return null; 
    	}
    	return sBitmapCache.get(key);
    }
    
    @SuppressLint("NewApi")
	public static int sizeOfBitmap(Bitmap bitmap) {
    	if(bitmap == null) {
    		return 0;
    	}
    	//API 19及以上版本
    	if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    		return bitmap.getAllocationByteCount();
    	//API 12 ~ API 18
    	} else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
    		return bitmap.getByteCount();
    	//API 12以下
    	} else {
    		return bitmap.getRowBytes() * bitmap.getHeight();
    	}
    }
}

A:上述代碼有問題嗎?有的話有哪些?

B:sBitmapCache是類變量而不是成員變量,不管我new了多少個BitmapMemoryCache對象然後進行put操作,其實put的都是同一個sBitmapCache,這與我們直覺是相違背的。

A:怎麼改進?

B:可以考慮把sBitmapCache聲明稱非static的。

A:那假如現在我new了100個BitmapMemoryCache對象,也就是有100個sBitmapCache緩存池,按照代碼中一個sBitmapCache的緩存池大小是虛擬機最大堆大小的1/6,顯然100*1/6大大超過了虛擬機的最大堆大小,這樣很容易導致OOM吧?

B:哦,那還是把sBitmapCache聲明成static吧,所有緩存統一管理,因爲虛擬機堆是共用,然後把BitmapMemoryCache做成單例就行。

A:這樣做可以,不過把BitmapMemoryCache做成單例後有一個問題,那個單例對象一直持有sBitmapCache引用,而sBitmapCache持有許多Bitmap的引用,因此在不用的時候要及時解除引用好讓垃圾回收器回收掉,否則會使一部分堆內存一直被佔用。

A:上面的代碼還有其它問題嗎?有沒有可能會發生OOM?

B:已經指定緩存池大小爲最大虛擬機堆大小的1/6了,因此緩存池最多就佔用1/6的最大虛擬機堆,應該不會發生OOM吧。

A:那如果其它地方佔用的堆大小超過5/6呢?

B:對哦,sBitmapCache最多允許放1/6的數據,而實際上如果可用堆已經少於最大堆的1/6了,這時再put就有可能發生OOM。

A:那需要怎麼改進?

B:在put方法裏,每次要將緩存添加到sBitmapCache時,先判斷一下可用堆大小還有多少,如果已經小於一個臨界值那麼我就不緩存該Bitmap,或者按照一定的規則(比如LRU算法)把緩存池裏面的某些Bitmap移除掉再添加當前Bitmap進去。

###二、深入理解LruCache實現原理

A:LruCache是怎麼實現LRU算法的?

B:其內部使用的是一個LinkedHashMap保存緩存數據,而LinkedHashMap其實已經實現了LRU算法,因此LruCache其實是使用了LinkedHashMap的實現。

A:能否具體說一下LinkedHashMap的LRU算法是怎麼實現的。

B:LinkedHashMap默認情況下並沒有開啓LRU功能,因爲是使用雙向循環鏈表實現,因此其迭代順序就是添加順序,但我們可以在其構造函數指定其開啓LRU功能,具體到代碼就是指定accessOrder爲true,然後每次訪問該LinkedHashMap時,它會把該節點移到鏈表的尾部,這樣越靠近鏈頭就是越少訪問的數據了,以後超出最大容量了直接從鏈頭開始刪起就行。

A:你說的是v4包下的LruCache吧,其實在Android3.1開始,android.util包下也有一個LruCache,它們是完全一樣的嗎?有沒有什麼區別?

B:一樣

A:LruCache是線程安全的嗎?

B:是,在put和get操作LruCache都進行了加鎖操作(使用this作爲鎖對象),因此是線程安全的。

A:你覺得加鎖方式有改進的地方?

B:get操作並不會修改數據,可以使用Java中ReadWriteLock讀寫鎖實現鎖機制,這樣效率會高一點。

A:LruCache裏面有一個叫create方法,它是做什麼用的?

B:在get操作發現沒有要獲取的元素時,會調用該方法,子類可以重寫該方法實現在獲取不到元素時自行創建相應的元素,這時get返回的可能就是這個元素。create該方法默認實現返回null。

A:爲什麼說可能返回create方法返回的那個元素呢,爲什麼不是一定?

B:因爲在調用create並對存放數據的map沒有加鎖,這時可能會有另外一個線程put一個相同key的元素進map,這時可以選擇直接返回map中的值,也可以選擇返回create返回的值,當然LruCache採用第一種方法實現。

A:最後在問一個,知道putCount,createCount,evictionCount,hitCount和missCount的含義嗎?

B:putCount表示進行put的操作次數,createCount表示調用create的次數,evictionCount表示被移除的元素數,hitCount表示目中次數,missCount表示未命中次數。

轉載請註明原文地址:http://blog.csdn.net/u012619640/article/details/50525464

本博客已停止更新,轉移到微信公衆號上寫文章,歡迎關注:Android進階驛站
Android進階驛站

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章