glide緩存分為內(nèi)存緩存和磁盤緩存。內(nèi)存緩存分為活動緩存和cache捉捅。磁盤緩存又分為resource和data闯捎。本文將圍繞加載圖片流程介紹glide的緩存。
Glide.with(this)
.load(url)
.skipMemoryCache(true)
// .transform(CircleCrop())
// .error(R.drawable.ic_launcher_background)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.listener(getListener(index))
.into(mIv)
1叙谨、load
public <R> LoadStatus load(
GlideContext glideContext,
Object model) {
long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
EngineKey key =
keyFactory.buildKey(
model,
signature,
width,
height);
EngineResource<?> memoryResource;
synchronized (this) {
memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
if (memoryResource == null) {
return waitForExistingOrStartNewJob(
glideContext,
model,
signature,
width,
height);
}
}
cb.onResourceReady(
memoryResource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
return null;
}
1.1温鸽、loadFromMemory 先從內(nèi)從中加載。
//Engine
private EngineResource<?> loadFromMemory(
EngineKey key, boolean isMemoryCacheable, long startTime) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> active = loadFromActiveResources(key);
if (active != null) {
return active;
}
EngineResource<?> cached = loadFromCache(key);
if (cached != null) {
return cached;
}
return null;
}
可以看到從內(nèi)存加載又分為活動緩存和 cache
1.11、活動緩存
舉例: 一個LinearLayout中有10個imageview涤垫,通過glide加載姑尺。那么這10個圖片就緩存在activityResource中。 當點擊的條目蝠猬,刪除一個item. 這個ActivityResource緩存并沒有被移除切蟋。當gc運行時,將這個item回收掉之后了榆芦,就會將這個緩存加入到resourceReferenceQueue柄粹,然后通過遍歷這個引用隊列,從活動緩存中移除匆绣。
舉例: 當在recyclerview的每個item加載一個imagview驻右。 那么這個活動緩存的最大數(shù)目為= 可見數(shù)據(jù)+ 預加載數(shù)目 + 復用池1 ,因為從recyclerview移除之后崎淳,不一定就被立即回收了堪夭,當從復用池中復用item時,會主動釋放上一次item對應的活動緩存凯力。此時活動緩存就減少了茵瘾。
活動緩存是何時存入的了?
1)咐鹤、從緩存中獲取到資源時拗秘,加入到活動緩存。
private EngineResource<?> loadFromCache(Key key) {
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
activeResources.activate(key, cached);
}
return cached;
}
2)祈惶、從網(wǎng)絡和磁盤加載資源完成時雕旨,加入到活動緩存。
活動緩存何時刪除的了捧请?
1)凡涩、onDestroy時,request調(diào)用clear() 會釋放當前對應key的活動緩存疹蛉。
2)活箕、不能設(shè)置資源給target,釋放當前key的活動緩存可款。
3)育韩、獲取活動緩存,發(fā)現(xiàn)該活動緩存EngineResource不存在闺鲸,就刪除key的活動緩存筋讨。也就是setIsActiveResourceRetentionAllowed(false)的情況。
4)摸恍、創(chuàng)建ActiveResources對象時悉罕,就開啟了一個線程,一直輪詢這個queue,如果存在的EngineResource壁袄,就刪除對應的活動緩存类早。
1.12、內(nèi)存緩存
獲取內(nèi)存時就從內(nèi)存緩存中移除然想。
//Engine
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;
}
何時加入內(nèi)存
從活動緩存移除就加入了內(nèi)存緩存莺奔。內(nèi)存緩存超過容量后被釋放的bitmap等會被加入到bitmapPool中欣范。從活動緩存中移除的要么加入內(nèi)存緩存变泄,要么加入到bitmapPool中。
//Engine
public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
activeResources.deactivate(cacheKey);
if (resource.isMemoryCacheable()) {
//加入內(nèi)存緩存
cache.put(cacheKey, resource);
} else {
//資源釋放恼琼,bitmap支持復用則加入到bitmapPool
resourceRecycler.recycle(resource, /* forceNextFrame= */ false);
}
}
//BitmapResource
@Override
public void recycle() {
bitmapPool.put(bitmap);
}
緩存資源的key是通過寬高,加載圖片的model妨蛹、options等生成的。所以寬高變化晴竞,可能導致內(nèi)存中找不到這張圖蛙卤。
1.2、內(nèi)存不存在噩死,開啟一個job從網(wǎng)絡加載加載颤难。
private <R> Engine.LoadStatus waitForExistingOrStartNewJob(
GlideContext glideContext,
Object model,
Key signature,
int width,
int height) {
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb, callbackExecutor);
return new Engine.LoadStatus(cb, current);
}
EngineJob<R> engineJob =
engineJobFactory.build(
key,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache);
DecodeJob<R> decodeJob =
decodeJobFactory.build(
glideContext,
model,
key,
signature,
width,
height,
engineJob);
jobs.put(key, engineJob);
engineJob.addCallback(cb, callbackExecutor);
engineJob.start(decodeJob);
return new Engine.LoadStatus(cb, engineJob);
}
可以看到先判斷,是否是從從緩存中取獲取緩存job已维。
如果緩存的job沒有找到行嗤。則創(chuàng)建一個job。并且執(zhí)行這個RunableJob垛耳。
public synchronized void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
GlideExecutor executor =
decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor();
executor.execute(decodeJob);
}
所以接下來栅屏,我們看下這個runnable(DecodeJob)的run方法。
public void run() {
DataFetcher<?> localFetcher = currentFetcher;
try {
runWrapped();
}
}
private void runWrapped() {
switch (runReason) {
case INITIALIZE:
stage = getNextStage(Stage.INITIALIZE);
currentGenerator = getNextGenerator();
runGenerators();
break;
case SWITCH_TO_SOURCE_SERVICE:
runGenerators();
break;
case DECODE_DATA:
decodeFromRetrievedData();
break;
}
}
private void runGenerators() {
currentThread = Thread.currentThread();
startFetchTime = LogTime.getLogTime();
boolean isStarted = false;
while (!isCancelled
&& currentGenerator != null
&& !(isStarted = currentGenerator.startNext())) {
stage = getNextStage(stage);
currentGenerator = getNextGenerator();
}
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;
}
private DataFetcherGenerator getNextGenerator() {
switch (stage) {
case RESOURCE_CACHE:
return new ResourceCacheGenerator(decodeHelper, this);
case DATA_CACHE:
return new DataCacheGenerator(decodeHelper, this);
case SOURCE:
return new SourceGenerator(decodeHelper, this);
case FINISHED:
return null;
default:
throw new IllegalStateException("Unrecognized stage: " + stage);
}
}
可以看到堂鲜,根據(jù)當前的狀態(tài)和diskCacheStrategy磁盤策略的配置栈雳,來決定是加載RESOURCE_CACHE、DATA_CACHE缔莲、SOURCE哥纫。
RESOURCE_CACHE : 是轉(zhuǎn)換過或者改變過采樣率的文件。解碼之后的痴奏。
DATA_CACHE:原始的未修改的數(shù)據(jù)蛀骇。解碼之前的。
SOURCE :網(wǎng)絡文件抛虫,源文件松靡。
1.3、INITIALIZE
如果配置全部允許建椰,會首先去加載Resource_cache雕欺,再加載DATA_CACHE,如果都加載不了,再從網(wǎng)絡加載SOURCE屠列。
ResourceCacheGenerator和DataCacheGenerator會從磁盤緩存中加載數(shù)據(jù)啦逆,這里不看了,看下從Remote加載笛洛,SourceGenerator從網(wǎng)絡加載數(shù)據(jù)夏志。
// SourceGenerator
public boolean startNext() {
if (dataToCache != null) {
Object data = dataToCache;
boolean isDataInCache = cacheData(data);
dataToCache = null;
}
if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
return true;
}
sourceCacheGenerator = null;
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
loadData = helper.getLoadData().get(loadDataListIndex++);
if (loadData != null
&& (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
|| helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
started = true;
startNextLoad(loadData);
}
}
return started;
}
private void startNextLoad(final ModelLoader.LoadData<?> toStart) {
loadData.fetcher.loadData(
helper.getPriority(),
new DataFetcher.DataCallback<Object>() {
@Override
public void onDataReady(@Nullable Object data) {
if (isCurrentRequest(toStart)) {
onDataReadyInternal(toStart, data);
}
}
@Override
public void onLoadFailed(@NonNull Exception e) {
if (isCurrentRequest(toStart)) {
onLoadFailedInternal(toStart, e);
}
}
});
}
void onDataReadyInternal(LoadData<?> loadData, Object data) {
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
dataToCache = data;
cb.reschedule();
} else {
cb.onDataFetcherReady(
loadData.sourceKey,
data,
loadData.fetcher,
loadData.fetcher.getDataSource(),
originalKey);
}
}
首先從網(wǎng)絡loadData.fetcher.loadData中加載,然后判斷isDataCacheable()是否可以緩存data苛让,這個配置是緩存策略中配置的沟蔑。如果可以緩存,調(diào)用cb.reschedule()狱杰。
1.4瘦材、SWITCH_TO_SOURCE_SERVICE
private void reschedule(RunReason runReason) {
this.runReason = runReason;
callback.reschedule(this);
}
@Override
public void reschedule() {
reschedule(RunReason.SWITCH_TO_SOURCE_SERVICE);
}
這個方法會再次調(diào)用run()方法,狀態(tài)變?yōu)镾WITCH_TO_SOURCE_SERVICE從網(wǎng)絡獲取數(shù)據(jù)切換到從磁盤獲取仿畸, 還會再次執(zhí)行要這個類食棕。 此時startNext方法中的dataToCache不為空,并且會調(diào)用將數(shù)據(jù)緩存到磁盤中错沽,然后調(diào)用DataCacheGenerator.startNext()簿晓,調(diào)用磁盤緩存加載,返回磁盤加載的結(jié)果千埃。
// SourceGenerator
private boolean cacheData(Object dataToCache) throws IOException {
try {
DataRewinder<Object> rewinder = helper.getRewinder(dataToCache);
Object data = rewinder.rewindAndGet();
Encoder<Object> encoder = helper.getSourceEncoder(data);
DataCacheWriter<Object> writer = new DataCacheWriter<>(encoder, data, helper.getOptions());
DataCacheKey newOriginalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
DiskCache diskCache = helper.getDiskCache();
diskCache.put(newOriginalKey, writer);
if (diskCache.get(newOriginalKey) != null) {
originalKey = newOriginalKey;
sourceCacheGenerator =
new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
return true;
} else {
cb.onDataFetcherReady(
loadData.sourceKey,
rewinder.rewindAndGet(),
loadData.fetcher,
loadData.fetcher.getDataSource(),
loadData.sourceKey);
}
return false;
}
}
上面就是在SourceGenerator中緩存數(shù)據(jù)Stream到磁盤過程憔儿。
1.5、DECODE_DATA
從getNextStage分析完INITIALIZE镰禾、SWITCH_TO_SOURCE_SERVICE皿曲,接下看下DECODE_DATA狀態(tài),從網(wǎng)絡請求對應的HttpGlideUrlLoader吴侦,返回的輸入流數(shù)據(jù)是ContentLengthInputStream屋休。
從磁盤緩存對應的FileLoader,返回的輸入流是文件輸入流备韧。
對于這些流需要轉(zhuǎn)化成Bitmap劫樟、Drawable等。
decode的過程 就是將InputStream 轉(zhuǎn)化成bitmap的過程织堂。
//DecodeJob
private void decodeFromRetrievedData() {
Resource<R> resource = null;
try {
resource = decodeFromData(currentFetcher, currentData, currentDataSource);
} catch (GlideException e) {
}
if (resource != null) {
notifyEncodeAndRelease(resource, currentDataSource, isLoadingFromAlternateCacheKey);
} else {
runGenerators();
}
}
//DecodePath
public Resource<Transcode> decode(
DataRewinder<DataType> rewinder,
int width,
int height,
@NonNull Options options,
DecodeCallback<ResourceType> callback)
throws GlideException {
Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options);
Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);
return transcoder.transcode(transformed, options);
}
//DecodeJob
<Z> Resource<Z> onResourceDecoded(DataSource dataSource, @NonNull Resource<Z> decoded) {
@SuppressWarnings("unchecked")
Class<Z> resourceSubClass = (Class<Z>) decoded.get().getClass();
Transformation<Z> appliedTransformation = null;
Resource<Z> transformed = decoded;
if (dataSource != DataSource.RESOURCE_DISK_CACHE) {
appliedTransformation = decodeHelper.getTransformation(resourceSubClass);
transformed = appliedTransformation.transform(glideContext, decoded, width, height);
}
final EncodeStrategy encodeStrategy;
final ResourceEncoder<Z> encoder;
if (decodeHelper.isResourceEncoderAvailable(transformed)) {
encoder = decodeHelper.getResultEncoder(transformed);
encodeStrategy = encoder.getEncodeStrategy(options);
} else {
encoder = null;
encodeStrategy = EncodeStrategy.NONE;
}
Resource<Z> result = transformed;
boolean isFromAlternateCacheKey = !decodeHelper.isSourceKey(currentSourceKey);
if (diskCacheStrategy.isResourceCacheable(
isFromAlternateCacheKey, dataSource, encodeStrategy)) {
final Key key;
switch (encodeStrategy) {
case SOURCE:
key = new DataCacheKey(currentSourceKey, signature);
break;
case TRANSFORMED:
key =
new ResourceCacheKey(
decodeHelper.getArrayPool(),
currentSourceKey,
signature,
width,
height,
appliedTransformation,
resourceSubClass,
options);
break;
default:
throw new IllegalArgumentException("Unknown strategy: " + encodeStrategy);
}
LockedResource<Z> lockedResult = LockedResource.obtain(transformed);
deferredEncodeManager.init(key, encoder, lockedResult);
result = lockedResult;
}
return result;
}
上面第一步是decode的流程叠艳,將stream轉(zhuǎn)化成bitmap,設(shè)置采樣率等易阳,transformed附较,可以看到在解析完成調(diào)用了diskCacheStrategy.isResourceCacheable()這個方法主要是為了初始化Resouce_cache相關(guān)信息。
// DecodeJob
private void notifyEncodeAndRelease(
Resource<R> resource, DataSource dataSource, boolean isLoadedFromAlternateCacheKey) {
GlideTrace.beginSection("DecodeJob.notifyEncodeAndRelease");
try {
Resource<R> result = resource;
LockedResource<R> lockedResource = null;
if (deferredEncodeManager.hasResourceToEncode()) {
lockedResource = LockedResource.obtain(resource);
result = lockedResource;
}
notifyComplete(result, dataSource, isLoadedFromAlternateCacheKey)
stage = Stage.ENCODE;
try {
if (deferredEncodeManager.hasResourceToEncode()) {
deferredEncodeManager.encode(diskCacheProvider, options);
}
} finally {
onEncodeComplete();
} finally {
GlideTrace.endSection();
}
}
private void notifyComplete(
Resource<R> resource, DataSource dataSource, boolean isLoadedFromAlternateCacheKey) {
setNotifiedOrThrow();
callback.onResourceReady(resource, dataSource, isLoadedFromAlternateCacheKey);
}
void encode(DiskCacheProvider diskCacheProvider, Options options) {
GlideTrace.beginSection("DecodeJob.encode");
try {
diskCacheProvider
.getDiskCache()
.put(key, new DataCacheWriter<>(encoder, toEncode, options));
} finally {
toEncode.unlock();
GlideTrace.endSection();
}
}
notifyComplete就是調(diào)用callback.onResourceReady這個方法最終就回調(diào)到我們給request設(shè)置的RequestListener中潦俺。
上面我們知道緩存Resource相關(guān)key已經(jīng)初始化了拒课,接著deferredEncodeManager.encode將resource緩存在本地徐勃,整個加載過程就結(jié)束。
上面我們一直提到磁盤緩存策略早像,那么我們看一下DiskCacheStrategy.ALL的代碼.
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;
}
};
isDataCacheable 表示可以將原始數(shù)據(jù)data緩存僻肖。
isResourceCacheable 表示可以將Resouce數(shù)據(jù)緩存。
decodeCachedResource 表示可以從Resource 中加載數(shù)據(jù)卢鹦。
decodeCachedData 表示可以從Data中加載數(shù)據(jù)臀脏。
上面這幅圖展示了glide緩存加載流程,沒有BitmapPool冀自,BitmapPool 只是從磁盤緩存揉稚、網(wǎng)絡加載圖片時,decode圖片需要復用的之前圖片的內(nèi)存凡纳,從bitmappool中獲取窃植。 活動內(nèi)存釋放帝蒿、cache的大小超過了容量會加入到bitmapPool荐糜。