Android圖片快取分析與優化
阿新 • • 發佈:2019-02-07
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欄,啟動程式並進行簡單操作,很清楚的看到記憶體佔用的實時變化以及釋放的過程。