前言
在前两篇中,主要进行了 Glide 三部曲 with、load、into 对应的源码解析。在本篇中将会对Glide的三级缓存以及三层缓存实现的逻辑进行详解。
在解读三级缓存之前,我们要先知道到底是哪三级缓存,然后再熟悉其原理,最后再将其原理带入源码中解读,将会事半功倍。
为了更好的了解Glide,建议没看过前两篇的小伙伴可以去看看,当然我这每一篇都是独立的,就算没看过前两篇,本篇内容依然能够看懂。
前两篇文章地址
# Android 架构之Glide源码解读(上)
# Android 架构之Glide源码解读(中)
1、三级缓存介绍
所谓的三级缓存,无非就是:
- 活动缓存 (ActiveCache)
- 内存缓存 (LruCache/MemoryCache)
- 磁盘缓存 (DiskLruCache)
接下来将会依次介绍这三级缓存。
这里的活动缓存,你可以吧它理解成一个Map集合,它是暂时性的,用完可丢的那种。需要它的时候就调用对应的Get/Put,不需要的时候就可以随时Clear掉。
而内存缓存(LruCache/MemoryCache)与磁盘缓存 (DiskLruCache),它们都是通过LinkedHashMap,进行了Lru算法,只不过它们的区别是一个存在内存里面生命周期和App运行时相互绑定,一个存在SD卡里可随着App的存在(未卸载)永久储存。
接下来我们来看看Lru算法是如何进行的。
如图所示
我们先设置了 LinkedHashMap 可保存的最大值为 3,同时依次向里面添加了3个元素,1作为最先添加的在最底部,3作为最后添加的在顶部。
当我们添加新的元素 4的时候,由于最多只能保存3个,所以作为1最先添加的元素要被移除掉,将最新添加的元素放在最顶部,2就因此放在了最底部。
在这个时候,我们在程序里又开始使用了2 的时候,因为2还在集合里面,所以将2的位置移动到了最上面,其他元素位置依次往下降一格。
我们先写一个小Demo 看看。
public static void main(String[] args) {
LinkedHashMap map = new LinkedHashMap<>(0, 0.75f, true);
map.put("one", 1);
map.put("two", 2);
map.put("three", 3);
map.put("four", 4);
map.put("five", 5);
map.get("three");
Map.Entry toEvict = map.entrySet().iterator().next();
System.out.println("First: "+toEvict.getValue());
for (Map.Entry stringIntegerEntry : map.entrySet()) {
System.out.println("element: "+stringIntegerEntry.getValue());
}
}
代码解读
这里定义了一个 LinkedHashMap 集合,然后往里面添加数据,接着使用了里面的某个元素对象,最后打印了集合最先添加的元素以及集合里面每一个元素。来看看运行效果。
运行效果
First: 1
element: 1
element: 2
element: 4
element: 5
element: 3
从这里面可以看出,我们按1-5的顺序依次添加在集合里,当我们调用map.get("three"); 的时候,就已经改变了原有集合里面的顺序。这里我们还没有设置MaxSize,那我们继续看看Lru的部分源码。
public void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(
getClass().getName() + ".sizeOf() is reporting inconsistent results!");
}
//判断是否达到最大值
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);
}
}
源码解读
从这里可以看出,当我们集合达到最大值的时候,就会获取集合里面第一个(最先最早)的元素,然后将它给移除掉。
到这相信你已经知道了Lru算法的工作原理,那么接下来我们来看看这三级缓存之间的关系。
2、三级缓存关系
如图所示
从这张图,我们可以开出三条支线:(我们先总结,然后依次在源码里验证)
第一条支线:当我们页面开始请求图片时,将会创建当且仅有一个空白的Fragment,并且与当前展示的Activity/Fragment的生命周期相互绑定。我们先假设三级缓存都没有对应的缓存,或者说图片是第一次请求的时候,将会去请求服务器图片地址。拿到图片后先写入磁盘缓存,接着会跳过内存缓存直接写入活动缓存,最后展示图片。
第二条支线:当对应展示图片的Activity/Fragment执行生命周期的onDestory方法时,对应Glide创建的Fragment也会执行相同的onDestory方法。当执行到 Glide的 onDestory方法时,会将当前页面的活动缓存全部添加至内存缓存里面,最后清空活动缓存。
第三条支线:当我们页面开始请求图片时,将会创建当且仅有一个空白的Fragment,并且与当前展示的Activity/Fragment的生命周期相互绑定,先去活动缓存里面查询是否有对应缓存,如果有则直接显示对应图片,终止图片请求;否则去内存缓存里面寻找。假如内存缓存里面有对应缓存,那么先将对应的内存缓存添加至对应的活动缓存并展示图片,然后移除内存缓存里面的缓存;假如内存缓存里面没有对应缓存,则进入磁盘缓存里面寻找,如果磁盘缓存里面有对应缓存,那么将对应的缓存,跳过内存缓存,直接添加至活动缓存。如果磁盘缓存里面没有对应缓存,那么执行第一条支线逻辑。
接下来就该验证这三条支线的逻辑了。
2.1 验证第一条支线
既然说的是,三级缓存都没有的情况,那我们直接定位到,图片从服务器请求成功的位置。(参考:Android 架构之Glide源码解读(中)
DecodeJob.class
private void notifyEncodeAndRelease(Resource resource, DataSource dataSource) {
...略
//通知上一级已完成
notifyComplete(result, dataSource);
stage = Stage.ENCODE;
try {
if (deferredEncodeManager.hasResourceToEncode()) {
//这里进行磁盘缓存
deferredEncodeManager.encode(diskCacheProvider, options);
}
} finally {
if (lockedResource != null) {
lockedResource.unlock();
}
}
onEncodeComplete();
}
源码解析
这里有两个核心代码,一是 调用了notifyComplete 方法;一是 调用了 deferredEncodeManager.encode 方法。
我们先追进 notifyComplete 方法,先看看最终干了什么。 步骤:notifyComplete->callback.onResourceReady->notifyCallbacksOfResult->engineJobListener.onEngineJobComplete,然后我们就能看到
@Override
public synchronized void onEngineJobComplete(
EngineJob> engineJob, Key key, EngineResource> resource) {
// A null resource indicates that the load failed, usually due to an exception.
if (resource != null && resource.isMemoryCacheable()) {
activeResources.activate(key, resource);
}
jobs.removeIfCurrent(key, engineJob);
}
源码解析
看到这,我们应该清楚了,在这进行了活动缓存的添加。接下来我们继续回到刚刚的位置。
DecodeJob.class
private void notifyEncodeAndRelease(Resource resource, DataSource dataSource) {
...略
//通知上一级已完成
notifyComplete(result, dataSource);
stage = Stage.ENCODE;
try {
if (deferredEncodeManager.hasResourceToEncode()) {
//这里进行磁盘缓存
deferredEncodeManager.encode(diskCacheProvider, options);
}
} finally {
if (lockedResource != null) {
lockedResource.unlock();
}
}
onEncodeComplete();
}
源码解析
进入 deferredEncodeManager.encode 方法,可得:
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();
}
}
源码解析
到这,我们验证了第一条支线完全符合逻辑。接下来轮到第二条支线。
2.2 验证第二条支线
第二条支线的起因点 在于onDestory方法,所以我们直接定位到Glide创建的Fragment里面的onDestory方法。
RequestManagerFragment.class
@Override
public void onDestroy() {
super.onDestroy();
lifecycle.onDestroy();
unregisterFragmentWithRoot();
}
源码解析
这里就两句代码,最后那句代码表示要移除相关Fragment操作,我们直接进入lifecycle.onDestroy()。
void onDestroy() {
isDestroyed = true;
for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
lifecycleListener.onDestroy();
}
}
源码解析
继续追进 onDestory方法
如图所示
进入对应方法。
@Override
public synchronized void onDestroy() {
targetTracker.onDestroy();
for (Target> target : targetTracker.getAll()) {
clear(target);
}
targetTracker.clear();
requestTracker.clearRequests();
lifecycle.removeListener(this);
lifecycle.removeListener(connectivityMonitor);
mainHandler.removeCallbacks(addSelfToLifecycle);
glide.unregisterRequestManager(this);
}
源码解析
这里我们继续追进,进入for循环里面的clear方法。过后步骤依次为: untrackOrDelegate->request.clear()->SingleRequest.clear()->engine.release(toRelease)->((EngineResource>) resource).release()->onResourceReleased,最后再进入实现这个方法的地方就得到:
@Override
public void onResourceReleased(Key cacheKey, EngineResource> resource) {
activeResources.deactivate(cacheKey);
if (resource.isMemoryCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource, /*forceNextFrame=*/ false);
}
}
源码解析
这里先是调用了 deactivate 方法,然后进行了是否需要内存缓存判断,如果需要那就存入内存缓存,那我们看看 deactivate 这个方法到底做了啥。
synchronized void deactivate(Key key) {
ResourceWeakReference removed = activeEngineResources.remove(key);
if (removed != null) {
removed.reset();
}
}
源码解析
从这里可以看出,先是调用了方法 deactivate 将活动缓存对应的图片移除掉,然后将移除掉的对应图片缓存添加至内存缓存里。
这里第二条支线也通过源码验证了,接下来该验证第三条支线了。
2.3 验证第三条支线
第三条支线的起因在于网络请求前判断,所以这里需要直接定位到,网络请求前判断的位置。
Engine.class
public LoadStatus load(
GlideContext glideContext,
Object model,
Key signature,
int width,
int height,
Class> resourceClass,
Class transcodeClass,
Priority priority,
DiskCacheStrategy diskCacheStrategy,
Map, 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) {
memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
if (memoryResource == null) {
return waitForExistingOrStartNewJob(
glideContext,
model,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
options,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache,
cb,
callbackExecutor,
key,
startTime);
}
}
// Avoid calling back while holding the engine lock, doing so makes it easier for callers to
// deadlock.
cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE);
return null;
}
源码解析
从这里可以看出,这里通过图片的一系列参数(地址、Url、宽高等等)生成了 EngineKey 参数,然后将参数带入了方法loadFromMemory,如果该方法返回的值不为空,就直接返回,接下来进入看看该方法。
@Nullable
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;
}
源码解析
从这里我们可以得到,如果活动缓存里面没有那么就去取内存缓存,如果内存缓存里面有那么就将内存缓存返回。但是在上面我们总结第三条支线的时候,还提到过当拿到对应内存缓存时,需要将对应缓存添加至活动缓存,并且移除当前内存缓存,这里并没有表现出来,接下来进入loadFromCache 方法看看。
private EngineResource> loadFromCache(Key key) {
EngineResource> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
activeResources.activate(key, cached);
}
return cached;
}
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();
}
}
源码解析
从这里我们可以得出:当获取到内存缓存时,会将对应缓存添加至活动缓存。那么删除对应的内存缓存呢?不急!不是还有个方法 getEngineResourceFromCache的嘛,接下来进入该方法看看。
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;
}
源码解析
果不出所料,移除内存缓存逻辑在这。接下来就差磁盘缓存的判断了。继续回到刚刚那个位置。
public LoadStatus load(
GlideContext glideContext,
Object model,
Key signature,
int width,
int height,
Class> resourceClass,
Class transcodeClass,
Priority priority,
DiskCacheStrategy diskCacheStrategy,
Map, 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) {
memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
if (memoryResource == null) {
return waitForExistingOrStartNewJob(
glideContext,
model,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
options,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache,
cb,
callbackExecutor,
key,
startTime);
}
}
// Avoid calling back while holding the engine lock, doing so makes it easier for callers to
// deadlock.
cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE);
return null;
}
源码解析
这里的 memoryResource 为空,表示的是:当前活动缓存以及内存缓存都为空。想看磁盘缓存还得往下走,步骤为:waitForExistingOrStartNewJob-> engineJob.start(decodeJob)->executor.execute(decodeJob) 这里开启了一个线程->DecodeJob->run方法,执行线程->runWrapped()->case INITIALIZE:->runGenerators->currentGenerator.startNext()->SourceGenerator.startNext()->sourceCacheGenerator.startNext()
这里步骤有点多,不知道,你们跟过来没有,这里代码为:
@Override
public boolean startNext() {
while (modelLoaders == null || !hasNextModelLoader()) {
sourceIdIndex++;
if (sourceIdIndex >= cacheKeys.size()) {
return false;
}
Key sourceId = cacheKeys.get(sourceIdIndex);
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
//从这里获取对应的磁盘缓存文件 cacheFile 为File类型
cacheFile = helper.getDiskCache().get(originalKey);
//如果 cacheFile 磁盘缓存不为空,那么 拿到 对应磁盘缓存的 modelLoaders
if (cacheFile != null) {
this.sourceKey = sourceId;
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
//将磁盘缓存的 modelLoaders 获取对应磁盘缓存的 modelLoader
ModelLoader modelLoader = modelLoaders.get(modelLoaderIndex++);
//cacheFile 为file类型。
loadData =
modelLoader.buildLoadData(
cacheFile, helper.getWidth(), helper.getHeight(), helper.getOptions());
if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
started = true;
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
源码解析
从这里可以看出,helper.getDiskCache().get(originalKey) 这句话表示获取对应图片的磁盘缓存,如果返回的 cacheFile 不为空,这说明有对应的磁盘缓存,那么下面while循环里面的 modelLoaders 为磁盘缓存对应的 modelLoaders,因为cacheFile为File类型, 也就是说下面的 buildLoadData 方法,也是调用的 File 类型的,因此进入FileLoader类:
public class FileLoader implements ModelLoader {
...略
@Override
public LoadData buildLoadData(
@NonNull File model, int width, int height, @NonNull Options options) {
return new LoadData<>(new ObjectKey(model), new FileFetcher<>(model, fileOpener));
}
...略
}
源码解析
进入 FileFetcher ,可得
@Override
public void loadData(@NonNull Priority priority, @NonNull DataCallback super Data> callback) {
try {
data = opener.open(file);
} catch (FileNotFoundException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Failed to open file", e);
}
callback.onLoadFailed(e);
return;
}
callback.onDataReady(data);
}
源码解析
好了,到这里应该都懂了吧,如果磁盘缓存不为空,那么进入 FileLoader类调用对应的文件加载方法。
3、总结
好了到这,这篇文章差不多结束了,相信看到这里的小伙伴应该完全理解了Glide三级缓存原理以及对应的关系。
相关推荐
【2021 最新版】Android studio全套教程+Android(安卓)开发入门到精通(项目实战篇)_哔哩哔哩_bilibili
【2021最新版】Kotlin语言教程——Kotlin入门到精通全系列_哔哩哔哩_bilibili
Android流行框架零基础入门到精通全套教程/热修复/Glide/插件化/Retrofit/OKHTTP/Gson/组件化/Jetpack/IOC/高德地图_哔哩哔哩_bilibili
Android开发进阶学习—设计思想解读开源框架 · 已更新至104集(持续更新中~)_哔哩哔哩_bilibili
【实战教学】Android开发—jetpack入门到精通_哔哩哔哩_bilibili
价值100W+Android实战项目大全/高级UI/灵动的锦鲤/QQ空间热修复/插件化框架/组件化框架设计/网络访问框架/RXJava/IOC/MVVM/NDK_哔哩哔哩_bilibili
Android音视频开发:音视频基础知识到直播推流实战系列教程_哔哩哔哩_bilibili
Android项目实战-从0开始手把手实现组件化路由SDK项目实战_哔哩哔哩_bilibili
【Android开发教程】一节课解剖Retrofit源码内核_哔哩哔哩_bilibili
本文转自 https://juejin.cn/post/7018130176785514504,如有侵权,请联系删除。