真正的傻瓜式,人人都看得懂
glide一共有幾級緩存
三級緩存大莫,activeResource,內(nèi)存緩存,硬盤緩存
有代碼為證:class Engine
//先從activeResource加載
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
//有結(jié)果就直接返回
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}
//從內(nèi)存緩存中加載
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
//都沒有挎袜,到異步線程去執(zhí)行邏輯,包括硬盤緩存跟請求網(wǎng)絡(luò)
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb, callbackExecutor);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Added to existing load", startTime, key);
}
return new LoadStatus(cb, current);
}
activeResource
比如如下的頁面肥惭,兩個imageview加載同樣的一張圖片盯仪,在內(nèi)存中,其實只有一份bitmap蜜葱,這個就是activeResource的功勞
通過glide加載的圖片資源全景,多個地方加載同個資源,都是返回同一份bitmap牵囤,避免bitmap重復(fù)(當(dāng)頁面銷毀爸黄,資源也會從activeResource中移除)
ActiveResources
代碼參考
//加入圖片的引用,內(nèi)部是一個hashmap的弱引用在管理
synchronized void activate(Key key, EngineResource<?> resource) {
ResourceWeakReference toPut =
new ResourceWeakReference(
key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);
ResourceWeakReference removed = activeEngineResources.put(key, toPut);
if (removed != null) {
removed.reset();
}
}
//移除引用
synchronized void deactivate(Key key) {
ResourceWeakReference removed = activeEngineResources.remove(key);
if (removed != null) {
removed.reset();
}
}
//查詢是否有某個圖片的引用
@Nullable
synchronized EngineResource<?> get(Key key) {
ResourceWeakReference activeRef = activeEngineResources.get(key);
if (activeRef == null) {
return null;
}
EngineResource<?> active = activeRef.get();
if (active == null) {
cleanupActiveReference(activeRef);
}
return active;
}
所以揭鳞,內(nèi)部的資源加載(各種R.drawable)炕贵,圖片加載,盡量使用glide來加載汹桦,是最好的選擇
那有個問題來了鲁驶,如果加載兩張一樣的圖片,但是imageview的寬高不一樣舞骆,還是內(nèi)存中只有一份bitmap嗎钥弯?
我們直接驗證看下
可以看到,內(nèi)存有兩張bitmap督禽,并且兩張bitmap的尺寸不一樣
這里就涉及到上面保存引用的key的邏輯了脆霎,可以直接看代碼
class EngineKeyFactory {
@SuppressWarnings("rawtypes")
EngineKey buildKey(Object model, Key signature, int width, int height,
Map<Class<?>, Transformation<?>> transformations, Class<?> resourceClass,
Class<?> transcodeClass, Options options) {
return new EngineKey(model, signature, width, height, transformations, resourceClass,
transcodeClass, options);
}
}
可以看到,key跟很多參數(shù)相關(guān)
model:就是加載的url
signature:額外的簽名
width狈惫,height:imageview的寬高
transformations:就是轉(zhuǎn)換睛蛛,比如centerCrop鹦马,roundCorner等
由于我們這里,只是寬高不一樣忆肾,所以為了保證同一個key荸频,保證寬高是一樣的就好,可以使用override方式
Glide.with(this).load(url).override(Target.SIZE_ORIGINAL).into(imageView)
這樣key是一樣客冈,在內(nèi)存中也就只有一份bitmap了旭从,提升了App性能
內(nèi)存緩存
內(nèi)存緩存的大小
有一套復(fù)雜的計算規(guī)則,不過大多數(shù)手機场仲,都是屏幕寬高*8和悦,常規(guī)的手機就是17M左右
詳細的計算規(guī)則可以查看類MemorySizeCalculator
對于大多數(shù)App,這個內(nèi)存是偏小的渠缕,通過直接放大1.5倍鸽素,代碼如下
Glide.get(context).setMemoryCategory(MemoryCategory.HIGH);
如果還是不夠的話,需要通過AppGlideModule
來實現(xiàn)
內(nèi)存緩存內(nèi)部是采用LinkedHashMap來實現(xiàn)的亦鳞,最新加的馍忽,放在最后面,緩存總大小到了上限值燕差,越早加入緩存的舵匾,越先被清空,可以看下構(gòu)造函數(shù)
/**
* Constructs an empty <tt>LinkedHashMap</tt> instance with the
* specified initial capacity, load factor and ordering mode.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @param accessOrder the ordering mode - <tt>true</tt> for
* access-order, <tt>false</tt> for insertion-order
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
關(guān)鍵是這個accessOrder
谁不,需要設(shè)置為true坐梯,這樣就會按照范圍的順序排序,最近被訪問或者使用的刹帕,就越不會被回收
獲取一個內(nèi)存緩存吵血,很簡單,就是從hashmap的get方法就可以
/**
* Returns the item in the cache for the given key or null if no such item exists.
*
* @param key The key to check.
*/
@Nullable
public synchronized Y get(@NonNull T key) {
return cache.get(key);
}
放入一個圖片到緩存中
/**
* Adds the given item to the cache with the given key and returns any previous entry for the
* given key that may have already been in the cache.
*
* <p>If the size of the item is larger than the total cache size, the item will not be added to
* the cache and instead {@link #onItemEvicted(Object, Object)} will be called synchronously with
* the given key and item.
*
* @param key The key to add the item at.
* @param item The item to add.
*/
@Nullable
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;
}
放入內(nèi)存緩存的時機是什么時候偷溺?蹋辅?
頁面銷毀的時候,證據(jù)如下
邏輯是這樣
加載到一張圖片挫掏,顯示在view上侦另,同時放入activeResource緩存,單view銷毀了尉共,從activeResource緩存中挪到memoryCache中
下次再打開頁面褒傅,加載同一種圖片,從memoryCache中袄友,remove掉該圖片殿托,加載到view里面,同時放入activeResource中
所以放入內(nèi)存緩存中的圖片剧蚣,都是不在頁面顯示的圖片
硬盤緩存
硬盤緩存的大小和默認緩存路徑
大兄е瘛:250M
路徑:/data/user/0/com.example.myapplication/cache/image_manager_disk_cache
interface Factory {
/** 250 MB of cache. */
int DEFAULT_DISK_CACHE_SIZE = 250 * 1024 * 1024;
String DEFAULT_DISK_CACHE_DIR = "image_manager_disk_cache";
/** Returns a new disk cache, or {@code null} if no disk cache could be created. */
@Nullable
DiskCache build();
}
問題:正寫入一個緩存文件到硬盤旋廷,App崩潰了,怎么辦礼搁?
答案:日志文件
一個日志文件饶碘,內(nèi)容大概這樣
* libcore.io.DiskLruCache
* 1
* 100
* 1
*
* CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
* DIRTY 335c4c6028171cfddfbaae1a9c313c52
* CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
* REMOVE 335c4c6028171cfddfbaae1a9c313c52
* DIRTY 1ab96a171faeeee38496d8b330771a7a
* CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
* READ 335c4c6028171cfddfbaae1a9c313c52
* READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
寫入一個緩存的邏輯是這樣
現(xiàn)在日志文件,寫一行DIRTY的日志
寫入真實的緩存的圖片
在日志文件馒吴,寫入一行CLEAN的日志
這樣熊镣,當(dāng)寫入的圖片失敗,或者App崩潰募书,日志里面,就只有DIRTH記錄测蹲,沒有CLEAN記錄莹捡,就可以把這次DIRTH的緩存給刪掉
加載一個緩存文件
通過日志文件,會生成一個硬盤緩存的hashmap扣甲,通過key索引篮赢,可以加載到這個拿到這個hashmap里面的值,可以實際拿到文件的緩存路徑
/**
* Returns a snapshot of the entry named {@code key}, or null if it doesn't
* exist is not currently readable. If a value is returned, it is moved to
* the head of the LRU queue.
*/
public synchronized Value get(String key) throws IOException {
checkNotClosed();
Entry entry = lruEntries.get(key);
if (entry == null) {
return null;
}
if (!entry.readable) {
return null;
}
for (File file : entry.cleanFiles) {
// A file must have been deleted manually!
if (!file.exists()) {
return null;
}
}
redundantOpCount++;
journalWriter.append(READ);
journalWriter.append(' ');
journalWriter.append(key);
journalWriter.append('\n');
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
return new Value(key, entry.sequenceNumber, entry.cleanFiles, entry.lengths);
}
刪除一個緩存
先刪除文件琉挖,然后往日志里面寫入REMOVE的標(biāo)記
public synchronized boolean remove(String key) throws IOException {
checkNotClosed();
Entry entry = lruEntries.get(key);
if (entry == null || entry.currentEditor != null) {
return false;
}
for (int i = 0; i < valueCount; i++) {
File file = entry.getCleanFile(i);
if (file.exists() && !file.delete()) {
throw new IOException("failed to delete " + file);
}
size -= entry.lengths[i];
entry.lengths[i] = 0;
}
redundantOpCount++;
journalWriter.append(REMOVE);
journalWriter.append(' ');
journalWriter.append(key);
journalWriter.append('\n');
lruEntries.remove(key);
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
return true;
}
刪除所有的硬盤緩存
public void delete() throws IOException {
close();
Util.deleteContents(directory);
}
日志文件路徑:
/data/user/0/com.example.myapplication/cache/image_manager_disk_cache/journal
緩存文件路徑:
/data/user/0/com.example.myapplication/cache/image_manager_disk_cache/6da652fb74c5ae6035ec3f7cab315b6f5dedb11515c173b2097d02a14c7974b9.0