前言
在上文Glide源碼解析之ActiveResources我們介紹了一級(jí)緩存ActiveResource,現(xiàn)在讓我們來(lái)看Glide的二級(jí)緩存MemoryCache。
二級(jí)緩存
在load()中首先會(huì)從ActiveResource中獲取EngineResource,如果獲取不到,接下來(lái)才會(huì)從MemoryCache中獲取。在getEngineResourceFromCache()中調(diào)用的MemoryCache的remove(),所以是會(huì)在MemoryCache中刪除這個(gè)Resource,并且在獲取成功后把EngineResource存入ActiveResource中。
在上文我們提到如果設(shè)置了isActiveResourceRetentionAllowed,則當(dāng)ActiveResource中的Resource被回收時(shí)又會(huì)存入MemoryCache中。Glide這樣設(shè)計(jì)可以保證最近活躍的資源可以最大限度的在內(nèi)存中緩存。
//Engine.load()
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
return null;
}
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
activeResources.activate(key, cached);
}
return cached;
}
private EngineResource<?> getEngineResourceFromCache(Key key) {
Resource<?> cached = cache.remove(key);
final EngineResource<?> result;
if (cached == null) {
result = null;
} else if (cached instanceof EngineResource) {
result = (EngineResource<?>) cached;
} else {
result = new EngineResource<>(cached, true /*isMemoryCacheable*/, true /*isRecyclable*/);
}
return result;
}
MemoryCache
MemoryCache是一個(gè)接口,在GlideBuilder的中會(huì)對(duì)MemoryCache進(jìn)行初始化,它的實(shí)現(xiàn)類是LruResourceCache。
//GlideBuilder
if (memoryCache == null) {
memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
}
LruResourceCache
LruResourceCache繼承了LruCache,但是使用的不是系統(tǒng)提供的LruCache,而是Glide自己實(shí)現(xiàn)的,并且實(shí)現(xiàn)了MemoryCache接口。
public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache {
}
LruCache
和系統(tǒng)的LruCache一樣都是使用了LinkedHashMap來(lái)實(shí)現(xiàn)最近最少使用原則,并且存放的元素總大小不能大于設(shè)定的大小。
public class LruCache<T, Y> {
//這是一個(gè)繼承了HashMap,但里面的Entry是使用LinkedHashMapEntry來(lái)作為一個(gè)雙鏈表,這樣既使用了HashMap的存取速度大多數(shù)情況下為O(1),又保證了順序。
//第三個(gè)參數(shù)為true代表鏈表按照訪問(wèn)順序存放,最近訪問(wèn)(存取)的放在鏈表的尾部。
private final Map<T, Y> cache = new LinkedHashMap<>(100, 0.75f, true);
private final long initialMaxSize; //初始化時(shí)最大的大小
private long maxSize; //最大的大小
private long currentSize; //當(dāng)前LinkedHashMap保存的元素的總大小
}
put()
首先判斷新元素的是否大小是否比總大小大,大的話則調(diào)用onItemEvicted()通知這個(gè)元素被刪除了,不存進(jìn)去。
接著加上計(jì)算新元素的大小,并存進(jìn)去,如果同一個(gè)key有舊元素的話則還要減去舊元素的大小,如果新舊不是同一個(gè)元素則也是調(diào)用onItemEvicted()通知舊元素被刪除了。最后再調(diào)用evict()來(lái)看當(dāng)前大小是否超出總大小,是的話則刪除最近最少使用的元素(鏈表頭)。
public synchronized Y put(@NonNull T key, @Nullable Y item) {
final int itemSize = getSize(item);
if (itemSize >= maxSize) {
onItemEvicted(key, item);
return null;
}
if (item != null) {
currentSize += itemSize;
}
@Nullable final Y old = cache.put(key, item);
if (old != null) {
currentSize -= getSize(old);
if (!old.equals(item)) {
onItemEvicted(key, old);
}
}
evict();
return old;
}
onItemEvicted()
這個(gè)方法主要就是當(dāng)元素被刪除時(shí)進(jìn)行通知的,由子類LruResourceCache進(jìn)行了重寫(xiě),最后會(huì)回調(diào)給Engine,對(duì)resource進(jìn)行回收處理。
protected void onItemEvicted(@NonNull T key, @Nullable Y item) {
// optional override
}
//LruResourceCache
protected void onItemEvicted(@NonNull Key key, @Nullable Resource<?> item) {
if (listener != null && item != null) {
listener.onResourceRemoved(item);
}
}
//Engine
@Override
public void onResourceRemoved(@NonNull final Resource<?> resource) {
resourceRecycler.recycle(resource);
}
evict()
這個(gè)方法是對(duì)當(dāng)前元素的總大小進(jìn)行處理,如果超過(guò)了maxSize,則會(huì)一直循環(huán)刪掉鏈表的第一個(gè)元素。因?yàn)樽罱L問(wèn)的元素是放在鏈表的最后的,所以這樣實(shí)現(xiàn)了最近最少使用的元素會(huì)被刪除。
private void evict() {
trimToSize(maxSize);
}
protected synchronized void trimToSize(long size) {
Map.Entry<T, Y> last;
Iterator<Map.Entry<T, Y>> cacheIterator;
while (currentSize > size) {
cacheIterator = cache.entrySet().iterator();
last = cacheIterator.next();
final Y toRemove = last.getValue();
currentSize -= getSize(toRemove);
final T key = last.getKey();
cacheIterator.remove();
onItemEvicted(key, toRemove);
}
}
remove()
根據(jù)key從LinkedHashMap中獲取value,如果不為空則當(dāng)前大小減去value的大小。
public synchronized Y remove(@NonNull T key) {
final Y value = cache.remove(key);
if (value != null) {
currentSize -= getSize(value);
}
return value;
}
getSize()
返回Resource的大小,如果是BitmapResource則返回的是Bitmap占用的內(nèi)存大小。
protected int getSize(@Nullable Resource<?> item) {
if (item == null) {
return super.getSize(null);
} else {
return item.getSize();
}
}
//LruResouceCache
@Override
protected int getSize(@Nullable Resource<?> item) {
if (item == null) {
return super.getSize(null);
} else {
return item.getSize();
}
}
//BitmapResouce
@Override
public int getSize() {
return Util.getBitmapByteSize(bitmap);
}
/**
* 返回Bitmap占用的內(nèi)存
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
public static int getBitmapByteSize(@NonNull Bitmap bitmap) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try {
return bitmap.getAllocationByteCount();
} catch (@SuppressWarnings("PMD.AvoidCatchingNPE") NullPointerException e) {
// Do nothing.
}
}
return bitmap.getHeight() * bitmap.getRowBytes();
}
trimMemory()
我們知道當(dāng)系統(tǒng)內(nèi)存緊張的時(shí)候會(huì)回調(diào)給ComponentCallbacks2,一般我們會(huì)在相應(yīng)的回調(diào)里清理內(nèi)存,防止被系統(tǒng)殺掉,而Glide同樣幫我們做了釋放內(nèi)存的操作。
//LruResouceCache
public void trimMemory(int level) {
if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
// 應(yīng)用已經(jīng)進(jìn)入系統(tǒng)的LRU應(yīng)用列表了
clearMemory();
} else if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
|| level == android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
// 應(yīng)用沒(méi)有UI正在顯示,或者是一個(gè)前臺(tái)應(yīng)用,但是當(dāng)前系統(tǒng)的內(nèi)存很緊張,無(wú)法保持后臺(tái)進(jìn)程的運(yùn)行。
trimToSize(getMaxSize() / 2); //清除到只剩一半內(nèi)存占用
}
}
//LruCache
public void clearMemory() {
trimToSize(0); //清除全部?jī)?nèi)存緩存
}
//Glide
public void trimMemory(int level) {
memoryCache.trimMemory(level);
bitmapPool.trimMemory(level);
arrayPool.trimMemory(level);
}
//ComponentCallbacks2
@Override
public void onTrimMemory(int level) {
trimMemory(level);
}
//初始化完Glide后就注冊(cè)ComponentCallbacks2
applicationContext.registerComponentCallbacks(glide);
MemoryCache最大占用內(nèi)存的大小
在GlideBuilder對(duì)MemoryCache初始化的時(shí)候使用的是memorySizeCalculator.getMemoryCacheSize(),那么接下來(lái)看一下MemorySizeCalculator是怎樣計(jì)算MemoryCacheSize的。
下面注釋寫(xiě)的聽(tīng)清楚,應(yīng)該能看懂,一般來(lái)說(shuō)最終MemoryCacheSize的大小就是兩張占滿屏幕的圖片的大小,以1080 X 1920的圖片來(lái)說(shuō)就是1080 X 1920 X 4 ≈ 8M。
//GlideBuilder
if (memoryCache == null) {
memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
}
MemorySizeCalculator(MemorySizeCalculator.Builder builder) {
this.context = builder.context;
//默認(rèn)為 4MB,如果是低內(nèi)存設(shè)備(運(yùn)存少于1GB,或者系統(tǒng)在4.4以下)則在此基礎(chǔ)上除以二
arrayPoolSize =
isLowMemoryDevice(builder.activityManager)
? builder.arrayPoolSizeBytes / LOW_MEMORY_BYTE_ARRAY_POOL_DIVISOR
: builder.arrayPoolSizeBytes;
//先獲取當(dāng)前進(jìn)程可使用內(nèi)存大小,
//然后判斷是否為低內(nèi)存設(shè)備再乘以相應(yīng)的系數(shù),
//普通設(shè)備是乘以 0.4,低內(nèi)存為 0.33,這樣得到的是 Glide 可使用的最大內(nèi)存大小
int maxSize =getMaxSize(
builder.activityManager, builder.maxSizeMultiplier, builder.lowMemoryMaxSizeMultiplier);
int widthPixels = builder.screenDimensions.getWidthPixels();
int heightPixels = builder.screenDimensions.getHeightPixels();
//計(jì)算一張格式為ARGB_8888,大小為屏幕大小的圖片的占用內(nèi)存大小
//BYTES_PER_ARGB_8888_PIXEL值為 4
int screenSize = widthPixels * heightPixels * BYTES_PER_ARGB_8888_PIXEL;
int targetBitmapPoolSize = Math.round(screenSize * builder.bitmapPoolScreens);
//memoryCacheScreens值為2
int targetMemoryCacheSize = Math.round(screenSize * builder.memoryCacheScreens);
//去掉 ArrayPool 占用的內(nèi)存后還剩余的內(nèi)存
int availableSize = maxSize - arrayPoolSize;
if (targetMemoryCacheSize + targetBitmapPoolSize <= availableSize) {
//未超出可用內(nèi)存限制
memoryCacheSize = targetMemoryCacheSize;
bitmapPoolSize = targetBitmapPoolSize;
} else {
//超出限制,則按比例縮小
float part = availableSize / (builder.bitmapPoolScreens + builder.memoryCacheScreens);
memoryCacheSize = Math.round(part * builder.memoryCacheScreens);
bitmapPoolSize = Math.round(part * builder.bitmapPoolScreens);
}
}
總結(jié)
作為Glide的二級(jí)緩存,MemoryCache使用LruCache來(lái)實(shí)現(xiàn)最近最少使用的原則,從而使Resource不會(huì)一直占用內(nèi)存,并且會(huì)在內(nèi)存緊張的時(shí)候自動(dòng)幫我們回收內(nèi)存,可以說(shuō)是考慮的很周到了。