前言
默認(rèn)情況下臭杰,Glide 會在開始一個(gè)新的圖片請求之前檢查以下多級的緩存:
- 活動(dòng)資源 (Active Resources) - 現(xiàn)在是否有另一個(gè) View 正在展示這張圖片?
- 內(nèi)存緩存 (Memory cache) - 該圖片是否最近被加載過并仍存在于內(nèi)存中毅戈?
- 資源類型(Resource) - 該圖片是否之前曾被解碼、轉(zhuǎn)換并寫入過磁盤緩存沐鼠?
- 數(shù)據(jù)來源 (Data) - 構(gòu)建這個(gè)圖片的資源是否之前曾被寫入過文件緩存肝箱?
前兩步檢查圖片是否在內(nèi)存中,如果是則直接返回圖片绵载。后兩步則檢查圖片是否在磁盤上埂陆,以便快速但異步地返回圖片苛白。
如果四個(gè)步驟都未能找到圖片,則Glide會返回到原始資源以取回?cái)?shù)據(jù)(原始文件焚虱,Uri, Url等)购裙。
緩存 Key
既然是緩存功能,就必然會有用于進(jìn)行緩存的 Key鹃栽。那么 Glide 的緩存 Key 是怎么生成的呢躏率?
Glide 的緩存 Key 生成規(guī)則非常繁瑣,決定緩存Key的參數(shù)竟然有 8 個(gè)民鼓。不過繁瑣歸繁瑣禾锤,至少邏輯還是比較簡單的,我們先來看一下 Glide 緩存 Key 的生成邏輯摹察。
生成緩存 Key 的代碼在 Engine 類的 load() 方法當(dāng)中恩掷,來看一下:
public class Engine
implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
...
public <R> LoadStatus load(
GlideContext glideContext,
Object model,
Key signature,
int width,
int height,
Class<?> resourceClass,
Class<R> transcodeClass,
Priority priority,
DiskCacheStrategy diskCacheStrategy,
Map<Class<?>, Transformation<?>> transformations,
boolean isTransformationRequired,
boolean isScaleOnlyOrNoTransform,
Options options,
boolean isMemoryCacheable,
boolean useUnlimitedSourceExecutorPool,
boolean useAnimationPool,
boolean onlyRetrieveFromCache,
ResourceCallback cb,
Executor callbackExecutor) {
long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
EngineKey key =
keyFactory.buildKey(
model,
signature,
width,
height,
transformations,
resourceClass,
transcodeClass,
options);
EngineResource<?> memoryResource;
synchronized (this) {
// ?? 1:下面要用到
memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
...
}
...
}
這里 model
就是我們要加載的圖片的唯一標(biāo)識,比如說如果是一張網(wǎng)絡(luò)上的圖片的話供嚎,那么這個(gè)id就是這張圖片的url地址黄娘。
接下來這行,將這個(gè) model
連同著signature克滴、width逼争、height等等 8 個(gè)參數(shù)一起傳入到 EngineKeyFactory 的 buildKey() 方法當(dāng)中,從而構(gòu)建出了一個(gè) EngineKey 對象劝赔,這個(gè) EngineKey 也就是 Glide 中的緩存 Key 了誓焦。
可見,決定緩存 Key 的條件非常多着帽,即使你用 override() 方法改變了一下圖片的 width
或者 height
杂伟,也會生成一個(gè)完全不同的緩存 Key。
EngineKey 類的源碼大家有興趣可以自己去看一下仍翰,其實(shí)主要就是重寫了 equals()
和 hashCode()
方法赫粥,保證只有傳入 EngineKey 的所有參數(shù)都相同的情況下才認(rèn)為是同一個(gè) EngineKey 對象。
內(nèi)存緩存
有了緩存 Key予借,接下來就可以開始進(jìn)行緩存了越平,那么我們先從內(nèi)存緩存看起。
首先你要知道灵迫,默認(rèn)情況下秦叛,Glide 自動(dòng)就是開啟內(nèi)存緩存的。
也就是說瀑粥,當(dāng)我們使用 Glide 加載了一張圖片之后挣跋,這張圖片就會被緩存到內(nèi)存當(dāng)中,只要在它還沒從內(nèi)存中被清除之前利凑,下次使用 Glide 再加載這張圖片都會直接從內(nèi)存當(dāng)中讀取浆劲,而不用重新從網(wǎng)絡(luò)或硬盤上讀取了嫌术。
如果你有什么特殊的原因需要禁用內(nèi)存緩存功能,Glide 對此提供了接口:
Glide.with(this)
.load(url)
.skipMemoryCache(true)
.into(imageView);
可以看到牌借,只需要調(diào)用 skipMemoryCache() 方法并傳入 true
度气,就表示禁用掉Glide的內(nèi)存緩存功能。
沒錯(cuò)膨报,關(guān)于Glide內(nèi)存緩存的用法就只有這么多磷籍,可以說是相當(dāng)簡單。接下來就讓我們就通過閱讀源碼來分析一下 Glide 的內(nèi)存緩存功能是如何實(shí)現(xiàn)的现柠。
其實(shí)說到內(nèi)存緩存的實(shí)現(xiàn)院领,非常容易就讓人想到 LruCache 算法(Least Recently Used),也叫近期最少使用算法够吩。
首先回憶一下比然,在上一篇文章的第三步 into() 方法中,我們當(dāng)時(shí)分析到了在 開始用引擎load:
方法的時(shí)候周循,說到過强法,先去緩存去找,如果沒有就去走 * waitForExistingOrStartNewJob* 方法
湾笛。當(dāng)時(shí)我們沒有展開說緩存那塊饮怯,那么我們現(xiàn)在來看下它的源碼,也就是上面 ??1 的那 loadFromMemory 方法嚎研,我們看一下代碼:
@Nullable
private EngineResource<?> loadFromMemory(
EngineKey key, boolean isMemoryCacheable, long startTime) {
if (!isMemoryCacheable) {
return null;
}
// 優(yōu)先加載內(nèi)存中的活動(dòng)緩存 - ActiveResources
EngineResource<?> active = loadFromActiveResources(key);
if (active != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return active;
}
// 如果活動(dòng)緩存中沒有蓖墅,就加載 LRU 內(nèi)存緩存中的資源數(shù)據(jù)。
EngineResource<?> cached = loadFromCache(key);
if (cached != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return cached;
}
return null;
}
可以看到临扮,首先就判斷了 isMemoryCacheable
是不是 false论矾,如果是 false 的話就直接返回 null。這是什么意思呢公条?
其實(shí)很簡單拇囊,我們剛剛不是學(xué)了一個(gè) skipMemoryCache() 方法嗎?如果在這個(gè)方法中傳入 true靶橱,那么這里的 isMemoryCacheable
就會是 false,表示內(nèi)存緩存已被禁用 Glide 的圖片加載過程中會調(diào)用兩個(gè)方法來獲取內(nèi)存緩存路捧,loadFromActiveResources() 和 loadFromCache() 关霸。
我們來看一下 loadFromActiveResources() 的源碼:
@Nullable
private EngineResource<?> loadFromActiveResources(Key key) {
EngineResource<?> active = activeResources.get(key);
if (active != null) {
active.acquire();
}
return active;
}
這里調(diào)用到了 ActiveResources 類里面的 * get* 方法,代碼如下:
final class ActiveResources {
@VisibleForTesting
final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
···
@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;
}
···
@VisibleForTesting
static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {
···
}
可以看到 activeResources
就是一個(gè)弱引用的 HashMap杰扫,用來緩存正在使用中的圖片队寇。我們可以看到,loadFromActiveResources() 方法就是從 activeResources
這個(gè) HashMap 當(dāng)中取值的章姓。使用 activeResources
來緩存正在使用中的圖片佳遣,可以保護(hù)這些圖片不會被 LruCache 算法回收掉识埋。
接下來再看一下 loadFromCache(key) 方法,在這個(gè)方法中零渐,會使用緩存 Key 來從 cache 當(dāng)中取值窒舟,而這里的 cache 對象就是在構(gòu)建 Glide 對象時(shí)創(chuàng)建的 LruResourceCache,那么說明這里其實(shí)使用的就是 LruCache 算法了诵盼。代碼如下:
public class Engine
implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
···
private final MemoryCache cache;
···
private EngineResource<?> loadFromCache(Key key) {
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) {
// Save an object allocation if we've cached an EngineResource (the typical case).
result = (EngineResource<?>) cached;
} else {
result =
new EngineResource<>(
cached, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ true, key, /*listener=*/ this);
}
return result;
}
先看 cache惠豺,這個(gè)是 MemoryCache ,MemoryCache 代碼如下:
/** An interface for adding and removing resources from an in memory cache. */
public interface MemoryCache {
···
可以看到是一個(gè)接口风宁,在 GlideBuilder 類中實(shí)現(xiàn)如下:
if (memoryCache == null) {
memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
}
而 LruResourceCache 從名字上看是實(shí)現(xiàn)LRU算法的洁墙,進(jìn)去看一下:
/** An LRU in memory cache for {@link com.bumptech.glide.load.engine.Resource}s. */
public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache {
···
果然是,那內(nèi)部就是 LinkedHashMap 實(shí)現(xiàn)的戒财,我們接著看 getEngineResourceFromCache 方法里面的 remove 方法热监,LinkedHashMap 的 remove 方法是,存在這個(gè)key對應(yīng)的值饮寞,那就刪除并且返回狼纬,沒有的話直接返回空。
為什么 Glide 會弄 2 個(gè)內(nèi)存緩存(一個(gè) Map + 弱引用骂际,一個(gè) LRU 內(nèi)存緩存?
郭神的回道是這樣的:ActiveResources 就是一個(gè)弱引用的 HashMap 疗琉,用來緩存正在使用中的圖片,使用 ActiveResources 來緩存正在使用中的圖片,可以保護(hù)這些圖片不會被 LruCache 算法回收掉歉铝。
DevYK的理解是這樣的:比如我們 Lru 內(nèi)存緩存 size 設(shè)置裝 99 張圖片盈简,在滑動(dòng) RecycleView 的時(shí)候,如果剛剛滑動(dòng)到 100 張太示,那么就會回收掉我們已經(jīng)加載出來的第一張柠贤,這個(gè)時(shí)候如果返回滑動(dòng)到第一張,會重新判斷是否有內(nèi)存緩存类缤,如果沒有就會重新開一個(gè) Request 請求臼勉,很明顯這里如果清理掉了第一張圖片并不是我們要的效果。所以在從內(nèi)存緩存中拿到資源數(shù)據(jù)的時(shí)候就主動(dòng)添加到活動(dòng)資源中餐弱,并且清理掉內(nèi)存緩存中的資源宴霸。這么做很顯然好處是 保護(hù)不想被回收掉的圖片不被 LruCache 算法回收掉,充分利用了資源。
好的膏蚓,從內(nèi)存緩存中讀取數(shù)據(jù)的邏輯大概就是這些了瓢谢。概括一下來說,就是如果能從內(nèi)存緩存當(dāng)中讀取到要加載的圖片驮瞧,那么就直接進(jìn)行回調(diào)氓扛,如果讀取不到的話,才會開啟線程執(zhí)行后面的圖片加載邏輯论笔。
硬盤緩存
調(diào)用如下代碼開啟磁盤緩存:
Glide.with(this)
.load(url)
.diskCacheStrategy(DiskCacheStrategy. RESOURCE)//使用磁盤資源緩存功能
.into(imageView);
這個(gè) diskCacheStrategy() 方法它可以接收五種參數(shù):
- DiskCacheStrategy.NONE: 表示不緩存任何內(nèi)容采郎。
- DiskCacheStrategy.RESOURCE: 表示只緩存轉(zhuǎn)換過后的圖片千所。
- DiskCacheStrategy.DATA: 表示只緩存原始圖片。
- DiskCacheStrategy.ALL : 表示既緩存原始圖片蒜埋,也緩存轉(zhuǎn)換過后的圖片淫痰。
- DiskCacheStrategy.AUTOMATIC:表示根據(jù)數(shù)據(jù)獲取器和編碼策略自動(dòng)選擇策略(默認(rèn))
默認(rèn)的策略叫做
AUTOMATIC
,它會嘗試對本地和遠(yuǎn)程圖片使用最佳的策略理茎。當(dāng)你加載遠(yuǎn)程數(shù)據(jù)(比如黑界,從URL下載)時(shí),AUTOMATIC
策略僅會存儲未被你的加載過程修改過(比如皂林,變換朗鸠,裁剪–譯者注)的原始數(shù)據(jù),因?yàn)橄螺d遠(yuǎn)程數(shù)據(jù)相比調(diào)整磁盤上已經(jīng)存在的數(shù)據(jù)要昂貴得多础倍。對于本地?cái)?shù)據(jù)烛占,AUTOMATIC
策略則會僅存儲變換過的縮略圖,因?yàn)榧词鼓阈枰俅紊闪硪粋€(gè)尺寸或類型的圖片沟启,取回原始數(shù)據(jù)也很容易忆家。
關(guān)于 Glide 硬盤緩存的用法也就只有這么多,那么接下來我們通過閱讀源碼來分析一下德迹,Glide的硬盤緩存功能是如何實(shí)現(xiàn)的芽卿。
DiskCacheStrategy.RESOURCE 資源類型
獲取資源數(shù)據(jù)
在上一篇文章中,創(chuàng)建 DecodeJob 并且調(diào)用 start 方法開始工作:
胳搞,當(dāng)時(shí)沒有說了不考慮緩存卸例,現(xiàn)在重新看一下緩存這塊,代碼如下:
class EngineJob<R> implements DecodeJob.Callback<R>, Poolable {
···
public synchronized void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
GlideExecutor executor =
decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor();
executor.execute(decodeJob);
}
···
}
可以看到肌毅,這里通過 willDecodeFromCache() 方法來判斷是否從磁盤緩存中讀取數(shù)據(jù)筷转,
/**
* Returns true if this job will attempt to decode a resource from the disk cache, and false if it
* will always decode from source.
*/
boolean willDecodeFromCache() {
Stage firstStage = getNextStage(Stage.INITIALIZE);
return firstStage == Stage.RESOURCE_CACHE || firstStage == Stage.DATA_CACHE;
}
private Stage getNextStage(Stage current) {
switch (current) {
case INITIALIZE:
return diskCacheStrategy.decodeCachedResource()
? Stage.RESOURCE_CACHE
: getNextStage(Stage.RESOURCE_CACHE);
case RESOURCE_CACHE:
return diskCacheStrategy.decodeCachedData()
? Stage.DATA_CACHE
: getNextStage(Stage.DATA_CACHE);
case DATA_CACHE:
// Skip loading from source if the user opted to only retrieve the resource from cache.
return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: " + current);
}
}
從代碼可以看到最終返回 true 還是 false 是根據(jù)我們選擇的 DiskCacheStrategy 模式來決定,上面說到了一共有五種模式悬而,這里看一下具體源碼呜舒,我們在選擇模式的時(shí)候,各個(gè)值對應(yīng)的結(jié)果是什么笨奠,這樣袭蝗,在 getNextStage 方法中邏輯結(jié)果了。
public abstract class DiskCacheStrategy {
public static final DiskCacheStrategy ALL =
new DiskCacheStrategy() {
@Override
public boolean isDataCacheable(DataSource dataSource) {
return dataSource == DataSource.REMOTE;
}
@Override
public boolean isResourceCacheable(
boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {
return dataSource != DataSource.RESOURCE_DISK_CACHE
&& dataSource != DataSource.MEMORY_CACHE;
}
@Override
public boolean decodeCachedResource() {
return true;
}
@Override
public boolean decodeCachedData() {
return true;
}
};
public static final DiskCacheStrategy NONE =
new DiskCacheStrategy() {
@Override
public boolean isDataCacheable(DataSource dataSource) {
return false;
}
@Override
public boolean isResourceCacheable(
boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {
return false;
}
@Override
public boolean decodeCachedResource() {
return false;
}
@Override
public boolean decodeCachedData() {
return false;
}
};
public static final DiskCacheStrategy DATA =
new DiskCacheStrategy() {
@Override
public boolean isDataCacheable(DataSource dataSource) {
return dataSource != DataSource.DATA_DISK_CACHE && dataSource != DataSource.MEMORY_CACHE;
}
@Override
public boolean isResourceCacheable(
boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {
return false;
}
@Override
public boolean decodeCachedResource() {
return false;
}
@Override
public boolean decodeCachedData() {
return true;
}
};
public static final DiskCacheStrategy RESOURCE =
new DiskCacheStrategy() {
@Override
public boolean isDataCacheable(DataSource dataSource) {
return false;
}
@Override
public boolean isResourceCacheable(
boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {
return dataSource != DataSource.RESOURCE_DISK_CACHE
&& dataSource != DataSource.MEMORY_CACHE;
}
@Override
public boolean decodeCachedResource() {
return true;
}
@Override
public boolean decodeCachedData() {
return false;
}
};
public static final DiskCacheStrategy AUTOMATIC =
new DiskCacheStrategy() {
@Override
public boolean isDataCacheable(DataSource dataSource) {
return dataSource == DataSource.REMOTE;
}
@Override
public boolean isResourceCacheable(
boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {
return ((isFromAlternateCacheKey && dataSource == DataSource.DATA_DISK_CACHE)
|| dataSource == DataSource.LOCAL)
&& encodeStrategy == EncodeStrategy.TRANSFORMED;
}
@Override
public boolean decodeCachedResource() {
return true;
}
@Override
public boolean decodeCachedData() {
return true;
}
};
public abstract boolean isDataCacheable(DataSource dataSource);
public abstract boolean isResourceCacheable(
boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy);
public abstract boolean decodeCachedResource();
public abstract boolean decodeCachedData();
}
看到這個(gè)類艰躺,應(yīng)該就很清楚了呻袭。那么我們默認(rèn)是 decodeJob.willDecodeFromCache() 默認(rèn)是 true 來分析磁盤緩存里面的邏輯,看過上一期就可以知道 decodeJob 其實(shí)就是 runnable腺兴,執(zhí)行 execute 方法其實(shí)是走到 DecodeJob 的 run() 方法中:
@Override
public void run() {
···
// 1. 執(zhí)行runWrapped
runWrapped();
···
}
上一期分析過這個(gè)方法了,我們直接看重點(diǎn)廉侧,* runWrapped();* 的源碼:
private void runWrapped() {
switch (runReason) {
case INITIALIZE:
// 2.找到執(zhí)行的狀態(tài)
stage = getNextStage(Stage.INITIALIZE);
// 3.找到具體執(zhí)行器
currentGenerator = getNextGenerator();
// 4.開始執(zhí)行
runGenerators();
break;
case SWITCH_TO_SOURCE_SERVICE:
runGenerators();
break;
case DECODE_DATA:
decodeFromRetrievedData();
break;
default:
throw new IllegalStateException("Unrecognized run reason: " + runReason);
}
}
上面分析 willDecodeFromCache() 方法的時(shí)候页响,有??的地方我們知道 Stage.INITIALIZE篓足,所以我們走到 case INITIALIZE:
,看一下getNextGenerator 方法的代碼:
private DataFetcherGenerator getNextGenerator() {
switch (stage) {
case RESOURCE_CACHE: // 3.1解碼后的資源執(zhí)行器
return new ResourceCacheGenerator(decodeHelper, this);
case DATA_CACHE: // 原始數(shù)據(jù)執(zhí)行器
return new DataCacheGenerator(decodeHelper, this);
case SOURCE: // 新的請求闰蚕,http 執(zhí)行器
return new SourceGenerator(decodeHelper, this);
case FINISHED:
return null;
default:
throw new IllegalStateException("Unrecognized stage: " + stage);
}
}
通過上面代碼可知栈拖,主要是執(zhí)行 currentGenerator.startNext() 就句代碼,currentGerator 是一個(gè)接口没陡,通過注釋 3.1 我們知道這里它的實(shí)現(xiàn)類是 ResourceCacheGenerator ,那么我們具體看下 ResourceCacheGenerator 的 startNext 函數(shù)涩哟;
class ResourceCacheGenerator implements DataFetcherGenerator,
DataFetcher.DataCallback<Object> {
...
@Override
public boolean startNext() {
...
while (modelLoaders == null || !hasNextModelLoader()) {
resourceClassIndex++;
...
//1. 拿到資源緩存 key
currentKey =
new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops
helper.getArrayPool(),
sourceId,
helper.getSignature(),
helper.getWidth(),
helper.getHeight(),
transformation,
resourceClass,
helper.getOptions());
//2. 通過 key 獲取到資源緩存
cacheFile = helper.getDiskCache().get(currentKey);
if (cacheFile != null) {
sourceKey = sourceId;
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
//3. 獲取一個(gè)數(shù)據(jù)加載器
ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
//3.1 為資源緩存文件,構(gòu)建一個(gè)加載器盼玄,這是構(gòu)建出來的是 ByteBufferFileLoader 的內(nèi)部類 ByteBufferFetcher
loadData = modelLoader.buildLoadData(cacheFile,
helper.getWidth(), helper.getHeight(), helper.getOptions());
if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
started = true;
//3.2 利用 ByteBufferFetcher 加載,最后把結(jié)果會通過回調(diào)給 DecodeJob 的 onDataFetcherReady 函數(shù)
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
...
}
通過上面注釋可以得到幾點(diǎn)信息
- 首先根據(jù) 資源 ID 等一些信息拿到資源緩存 Key
- 通過 key 拿到緩存文件
- 構(gòu)建一個(gè) ByteBufferFetcher 加載緩存文件
- 加載完成之后回調(diào)到 DecodeJob 中。
存儲資源數(shù)據(jù)
先來看下面一段代碼:
class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback,
Runnable,
Comparable<DecodeJob<?>>,
Poolable {
...
private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
....
stage = Stage.ENCODE;
try {
//1. 是否可以將轉(zhuǎn)換后的圖片緩存
if (deferredEncodeManager.hasResourceToEncode()) {
//1.1 緩存入口
deferredEncodeManager.encode(diskCacheProvider, options);
}
} finally {
...
}
onEncodeComplete();
}
}
void encode(DiskCacheProvider diskCacheProvider, Options options) {
GlideTrace.beginSection("DecodeJob.encode");
try {
//1.2 將 Bitmap 緩存到資源磁盤
diskCacheProvider.getDiskCache().put(key,
new DataCacheWriter<>(encoder, toEncode, options));
} finally {
toEncode.unlock();
GlideTrace.endSection();
}
}
通過上面我們知道 http 請求到圖片輸入流之后經(jīng)過一系列處理遂铡,轉(zhuǎn)換得到目標(biāo) Bitmap 資源宴胧,最后通過回調(diào)到 DecodeJob 進(jìn)行緩存起來。
清理資源緩存
- 用戶主動(dòng)通過系統(tǒng)來清理
- 卸載軟件
- 調(diào)用 DisCache.clear();
DiskCacheStrategy.DATA 原始數(shù)據(jù)類型
獲取原始數(shù)據(jù)
參考上小節(jié) DiskCacheStrategy.RESOURCE 獲取資源童番,不同的是把 ResourceCacheGenerator 換成 DataCacheGenerator 加載了精钮。
存儲原始數(shù)據(jù)
這里既然存的是原始數(shù)據(jù)那么我們直接從 http 請求之后的響應(yīng)數(shù)據(jù)開始查看,通過上一篇我們知道是在 HttpUrlFetcher 中請求網(wǎng)絡(luò)剃斧,直接定位到目的地:
public class HttpUrlFetcher implements DataFetcher<InputStream> {
@Override
public void loadData(@NonNull Priority priority,
@NonNull DataCallback<? super InputStream> callback) {
long startTime = LogTime.getLogTime();
try {
//1. 通過 loadDataWithRedirects 來進(jìn)行http 請求轨香,返回 InputStream
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
//2. 將請求之后的數(shù)據(jù)返回出去
callback.onDataReady(result);
} catch (IOException e) {
...
} finally {
...
}
}
}
根據(jù)注釋可以得知,這里主要用于網(wǎng)絡(luò)請求幼东,請求響應(yīng)數(shù)據(jù)回調(diào)給 MultiModelLoader 中臂容。我們看下 它具體實(shí)現(xiàn):
class MultiModelLoader<Model, Data> implements ModelLoader<Model, Data> {
...
@Override
public void onDataReady(@Nullable Data data) {
//如果數(shù)據(jù)不為空,那么就回調(diào)給 SourceGenerator
if (data != null) {
callback.onDataReady(data);
} else {
startNextOrFail();
}
}
....
}
這里的 callback 指的是 SourceGenerator 筋粗,繼續(xù)跟
class SourceGenerator implements DataFetcherGenerator,
DataFetcher.DataCallback<Object>,
DataFetcherGenerator.FetcherReadyCallback {
....
@Override
public void onDataReady(Object data) {
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
//1. 收到網(wǎng)絡(luò)下載好的圖片原始數(shù)據(jù)策橘,賦值給成員變量 dataToCache
dataToCache = data;
//2. 交給 EngineJob
cb.reschedule();
} else {
cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
loadData.fetcher.getDataSource(), originalKey);
}
}
....
}
通過上面注釋可以知道 cb.reschedule(); 最后回調(diào)到 EngineJob 類,會執(zhí)行 reschedule(DecodeJob<?> job) 函數(shù)的 getActiveSourceExecutor().execute(job); 用線程池執(zhí)行任務(wù)娜亿,最后又回到了 DecodeJob 的 run 函數(shù) 拿到執(zhí)行器DataCacheGenerator ,最終會在 SourceGenerator 的 startNext() 函數(shù)丽已,之前流程代碼我就不貼了,上面講了很多次了买决,相信大家應(yīng)該記得了沛婴,我們直接看 startNext() 函數(shù)吧:
class SourceGenerator implements DataFetcherGenerator,
DataFetcher.DataCallback<Object>,
DataFetcherGenerator.FetcherReadyCallback {
/**這個(gè)臨時(shí)的變量就是 http 請求回來的圖片原始數(shù)據(jù)
*/
private Object dataToCache;
@Override
public boolean startNext() {
....
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
//放入緩存
cacheData(data);
}
...
}
return started;
}
private void cacheData(Object dataToCache) {
long startTime = LogTime.getLogTime();
try {
Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
DataCacheWriter<Object> writer =
new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
//存儲原始數(shù)據(jù)
//通過 StreamEncoder encode 寫入文件
helper.getDiskCache().put(originalKey, writer);
} finally {
loadData.fetcher.cleanup();
}
sourceCacheGenerator =
new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
}
通過上面代碼得知,這里將原始數(shù)據(jù)寫入文件中了督赤。
清理資源緩存
- 用戶主動(dòng)通過系統(tǒng)來清理
- 卸載軟件
- 調(diào)用 DisCache.clear();
磁盤緩存小節(jié)
- 資源緩存是在把圖片轉(zhuǎn)換完之后才緩存嘁灯;
- 原始數(shù)據(jù)是網(wǎng)絡(luò)請求成功之后就寫入緩存;
申明:開始結(jié)束的圖片來源網(wǎng)上躲舌,侵刪