編輯:Android編程入門
LruCache的Lru指的是LeastRecentlyUsed,也就是近期最少使用算法。也就是說,當我們進行緩存的時候,如果緩存滿了,會先淘汰使用的最少的緩存對象。
為什麼要用LruCache?其實使用它的原因有很多,例如我們要做一個電子商務App,如果我們不加節制的向服務器請求大量圖片,那麼對於服務器來說是一個不少的負擔,其次,對於用戶來說,每次刷新都意味著流量的大量消耗以及長時間等待,所以緩存機制幾乎是每個需要聯網的App必須做的。
LruCache已經存在於官方的API中,所以無需添加任何依賴即可使用,而這個緩存只是一個內存緩存,並不能進行本地緩存,也就是說,如果內存不足,緩存有可能會失效,而且當App重啟的時候,緩存會重新開始生效。如果想要進行本地磁盤緩存,推薦使用DiskLruCache,雖然沒包含在官方API中,但是官方推薦我們使用,本文暫不討論。
使用方法:
使用LruCache其實非常簡單,下面以一個圖片緩存為例:
創建LruCache對象:
private static class StringBitmapLruCache extends LruCache<String, Bitmap> { public StringBitmapLruCache() { // 構造方法傳入當前應用可用最大內存的八分之一 super((int) (Runtime.getRuntime().maxMemory() / 1024 / 8)); } @Override // 重寫sizeOf方法,並計算返回每個Bitmap對象占用的內存 protected int sizeOf(String key, Bitmap value) { return value.getByteCount() / 1024; } @Override // 當緩存被移除時調用,第一個參數是表明緩存移除的原因,true表示被LruCache移除,false表示被主動remove移除,可不重寫 protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) { super.entryRemoved(evicted, key, oldValue, newValue); } @Override // 當get方法獲取不到緩存的時候調用,如果需要創建自定義默認緩存,可以在這裡添加邏輯,可不重寫 protected Bitmap create(String key) { return super.create(key); } }
LruCache<String, Bitmap> mLruCache = new StringBitmapLruCache();
把圖片寫入緩存:
mLruCache.put(name, bitmap);
從緩存讀取圖片:
mLruCache.get(name);
從緩存中刪除圖片:
mLruCache.remove(name);
使用的方法很簡單,一般我們直接通過get方法讀取緩存,如果返回Null,再通過網絡訪問圖片,訪問之後,再把圖片put到緩存中,這樣下次訪問就可以獲取到。
至此,我們已經基本了解了LruCache的用法,我們不需要進行任何的淘汰處理,LruCache會自動幫我們完成淘汰的工作。
源碼分析:
構造方法:
public LruCache(int maxSize) { if (maxSize <= 0) { throw new IllegalArgumentException("maxSize <= 0"); } this.maxSize = maxSize; this.map = new LinkedHashMap<K, V>(0, 0.75f, true); }
可以看到,構造方法中我們獲取了緩存的最大值,並且創建了一個LinkedHashMap對象,這個對象就是整個LruCache的關鍵,淘汰最少使用的算法,其實就是通過這個類來實現的,有興趣可以看看這個類的機制。
put方法:
public final V put(K key, V value) { if (key == null || value == null) { throw new NullPointerException("key == null || value == null"); } V previous; synchronized (this) { putCount++; size += safeSizeOf(key, value); previous = map.put(key, value); if (previous != null) { size -= safeSizeOf(key, previous); } } if (previous != null) { entryRemoved(false, key, previous, value); } trimToSize(maxSize); return previous; }
解析:put方法中,先計算插入的對象類型的大小,調用的方法是safeSizeOf,這個方法其實只是簡單的調用了我們在構造的時候重寫的sizeOf方法,如果返回負數,則拋出異常。接著把我們需要緩存的對象插入LinkedHashMap中,如果緩存中有這個對象,就把size復位。如果緩存中有這個key對應的對象,則調用entryRemoved方法,這個方法默認為空,但是如果我們需要在緩存更新之後進行一些記錄的話,可以通過在構造時重寫這個方法來做到。接下來,調用trimToSize方法,這個方法是去檢查當前的size有沒有超過maxSize,這裡我們看看源碼
public void trimToSize(int maxSize) { while (true) { K key; V value; synchronized (this) { if (size < 0 || (map.isEmpty() && size != 0)) { throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!"); } if (size <= maxSize || map.isEmpty()) { break; } Map.Entry<K, V> toEvict = map.entrySet().iterator().next(); key = toEvict.getKey(); value = toEvict.getValue(); map.remove(key); size -= safeSizeOf(key, value); evictionCount++; } entryRemoved(true, key, value, null); } }
可以看到,這裡的判斷邏輯也很簡單,通過不斷的檢查,如果超過maxSize,則從LinkedHashMap中剔除一個,直到size等於或者小於maxSize,這裡同樣會調用entryRemoved方法。
get方法:
public final V get(K key) { if (key == null) { throw new NullPointerException("key == null"); } V mapValue; synchronized (this) { mapValue = map.get(key); if (mapValue != null) { hitCount++; return mapValue; } missCount++; } /* * Attempt to create a value. This may take a long time, and the map * may be different when create() returns. If a conflicting value was * added to the map while create() was working, we leave that value in * the map and release the created value. */ V createdValue = create(key); if (createdValue == null) { return null; } synchronized (this) { createCount++; mapValue = map.put(key, createdValue); if (mapValue != null) { // There was a conflict so undo that last put map.put(key, mapValue); } else { size += safeSizeOf(key, createdValue); } } if (mapValue != null) { entryRemoved(false, key, createdValue, mapValue); return mapValue; } else { trimToSize(maxSize); return createdValue; } }
解析:這裡可以看到,當我們調用get方法的時候,直接從LinkedHashMap中get一個當前key的對象並返回,如果返回的為Null,則會調用create方法來創建一個對象,而create方法默認也是一個空方法,直接返回null,所以,如果你需要在get失敗的時候創建一個默認的對象,可以在構造的時候重寫create方法。如果重寫了create方法,那麼下面的代碼會被執行,先進行LinkedHashMap的插入方法,如果已經存在,則返回存在的對象,否則返回我們創建的對象。這裡可以看到,這裡重復判斷列表中是否已經存在相同的對象,原因是,如果create方法處理的時間過長,有可能create出來的對象已經被put到LinkedHashMap中了。
remove方法:
public final V remove(K key) { if (key == null) { throw new NullPointerException("key == null"); } 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; }
解析:這裡邏輯也很清晰,跟上面的兩個方法也很類似,就不唠嗑了。
其他的一些地方,看看源碼就行,睡了,晚安。
阿裡客戶端工程師試題簡析——Android應用的閃退(crash)分析1. 問題描述 閃退(Crash)是客戶端程序在運行時遭遇無法處理的異常或
下一篇本文演示用Android Studio寫一個最簡單的輸入法。界面和交互都很簡陋,只為剔肉留骨,彰顯寫一個Android輸入法的要點。1、打開Android Stud
1. ViewRoot ViewRoot是連接WindowManager與DecorView的紐帶,View的整個繪制流程的三大步(
activity_data1.xml<?xml version=1.0 encoding=utf-8?><LinearLayout xmlns:andr