前言
Glide是一款强大的Android图片加载库,它使用了三级缓存来提供高效的图片加载和显示功能。这三级缓存分别是活动缓存/内存缓存/磁盘缓存,总的来说,Glide的三级缓存机制可以帮助Android应用在图片加载方面实现快速、高效、节省资源的目标。不仅可以提高用户体验,还可以减少网络请求,降低对服务器的负载。:
活动缓存
活动缓存其实也属于内存缓存的一种,只不过活动缓存是用于存储当前正在使用中的资源的一部分内存缓存。
当Glide
加载图片时,资源会首先被加载到活动缓存中,以便在当前和即将到来的请求中快速访问。活动缓存的大小通常是受限制的,它不会占用太多的内存资源,这样可以确保Glide
在加载图片时不会过度消耗设备的内存。
我们首先看一下活动缓存调用的地方:
java
public <R> LoadStatus load(
GlideContext glideContext,
Object model,
...
ResourceCallback cb,
Executor callbackExecutor) {
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,
...
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, /* isLoadedFromAlternateCacheKey= */ false);
return null;
}
Engine.loadFromMemory
是我们活动缓存以及内存缓存的起点:
kotlin
@Nullable
private EngineResource<?> loadFromMemory(
EngineKey key, boolean isMemoryCacheable, long startTime) {
if (!isMemoryCacheable) {
return null;
}
// 从活动缓存中加载图片资源
EngineResource<?> active = loadFromActiveResources(key);
if (active != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return active;
}
// 从内存缓存中加载图片资源
EngineResource<?> cached = loadFromCache(key);
if (cached != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return cached;
}
return null;
}
通过loadFromActiveResources
,最终调用到ActiveResources.get
方法:
ini
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;
}
可以看到,ActiveResources
中存有一个Map
,Map
的key是Glide
为图片资源生成的EngineKey
,而value
是一个ResourceWeakReference
对象,而ResourceWeakReference
对象就是一个EngineResource
的弱引用,当通过弱引用获取到了指定的资源则返回,若得到的资源为空,证明该资源被回收了,则可以移除这一对<key, value>
了:
less
static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {
final Key key;
Resource<?> resource;
ResourceWeakReference(
@NonNull Key key,
@NonNull EngineResource<?> referent,
@NonNull ReferenceQueue<? super EngineResource<?>> queue,
boolean isActiveResourceRetentionAllowed) {
super(referent, queue);
this.key = Preconditions.checkNotNull(key);
this.resource =
referent.isMemoryCacheable() && isActiveResourceRetentionAllowed
? Preconditions.checkNotNull(referent.getResource())
: null;
isCacheable = referent.isMemoryCacheable();
}
void reset() {
resource = null;
clear();
}
}
}
整个活动缓存的获取思路简单清晰,就是根据指定key
来获取对应的资源弱引用,再通过弱引用拿到对应的资源信息。但这里涉及的弱引用相关知识点值得我们学习
弱引用
弱引用(Weak Reference)是在编程中常见的一种引用类型,其主要特点是在垃圾回收过程中,如果一个对象只被弱引用所引用,并且没有被其他强引用所引用,那么该对象就会被垃圾回收器回收。弱引用通常用来解决内存泄漏和循环引用等问题,主要知识点有:
强引用和弱引用的区别 | 使用场景 | WeakReference类 | ReferenceQueue |
---|---|---|---|
- 强引用(Strong Reference) :在Java中,我们通常使用的对象引用都是强引用。只要一个对象有强引用与之关联,垃圾回收器就不会回收该对象。 |
- 弱引用(Weak Reference) :弱引用不会增加对象的引用计数,如果一个对象只被弱引用所引用,而没有被强引用所引用,那么垃圾回收器会在下一次进行垃圾回收时回收这个对象。 | - 缓存:弱引用常用于实现缓存机制。当缓存中的对象不再被其他强引用引用时,垃圾回收器可以自动清理这些对象,避免缓存占用过多内存。
- 监听器 :在事件监听器中,使用弱引用可以避免因为监听器对象持有引用导致对象无法被垃圾回收,从而造成内存泄漏。 | 在Java中,弱引用可以通过
java.lang.ref.WeakReference
类来实现。这个类提供了一个构造函数,用于创建弱引用对象,并提供get()
方法用于获取弱引用所引用的对象。需要注意的是,由于弱引用所引用的对象可能已经被垃圾回收器回收,因此在使用get()
方法获取对象时,需要先进行null
检查。 | 弱引用可以与ReferenceQueue
结合使用。ReferenceQueue
是一个队列,当弱引用所引用的对象被垃圾回收器回收时,该弱引用会被放入ReferenceQueue
中。通过检查ReferenceQueue
中的元素,可以知道哪些对象已经被垃圾回收器回收。 |
在ActiveResources
中,对应弱引用的第一种使用场景,即缓存的使用,另外为了保证在弱引用的EngineResource
回收后,对应的Map中的<key, value>
对也能清除,ActiveResources
中将弱引用和ReferenceQueue
(引用队列)结合使用:
- 在新建
ResourceWeakReference
实例时传入引用队列ReferenceQueue
:
java
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();
}
}
- 开一个线程,轮询引用队列里是否有资源被回收:
csharp
@Synthetic
void cleanReferenceQueue() {
while (!isShutdown) {
try {
ResourceWeakReference ref = (ResourceWeakReference) resourceReferenceQueue.remove();
cleanupActiveReference(ref);
...
}
}
void cleanupActiveReference(@NonNull ResourceWeakReference ref) {
synchronized (this) {
// 删除<key, value>对
activeEngineResources.remove(ref.key);
if (!ref.isCacheable || ref.resource == null) {
return;
}
}
EngineResource<?> newResource =
new EngineResource<>(
ref.resource,
/* isMemoryCacheable= */ true,
/* isRecyclable= */ false,
ref.key,
listener);
listener.onResourceReleased(ref.key, newResource);
}
cleanReferenceQueue
中用了一个while
循环,不停的查询ReferenceQueue
是否有被回收的弱引用资源,但是这不会死循环吗?我们追踪resourceReferenceQueue.remove()
会发现以下代码:
java
/**
* Removes the next reference object in this queue, blocking until either
* one becomes available or the given timeout period expires.
*
* <p> This method does not offer real-time guarantees: It schedules the
* timeout as if by invoking the {@link Object#wait(long)} method.
*
* @param timeout If positive, block for up to <code>timeout</code>
* milliseconds while waiting for a reference to be
* added to this queue. If zero, block indefinitely.
*
* @return A reference object, if one was available within the specified
* timeout period, otherwise <code>null</code>
*
* @throws IllegalArgumentException
* If the value of the timeout argument is negative
*
* @throws InterruptedException
* If the timeout wait is interrupted
*/
public Reference<? extends T> remove(long timeout)
throws IllegalArgumentException, InterruptedException
{
if (timeout < 0) {
throw new IllegalArgumentException("Negative timeout value");
}
synchronized (lock) {
Reference<? extends T> r = reallyPollLocked();
if (r != null) return r;
long start = (timeout == 0) ? 0 : System.nanoTime();
for (;;) {
lock.wait(timeout);
r = reallyPollLocked();
if (r != null) return r;
if (timeout != 0) {
long end = System.nanoTime();
timeout -= (end - start) / 1000_000;
if (timeout <= 0) return null;
start = end;
}
}
}
}
可以看到,当引用队列中没有弱引用对象时,函数会阻塞在这里,而不会造成死循环的问题,可以说是非常的巧妙了。
内存缓存
内存缓存将已加载的图片数据存储在应用的内存中。由于内存读取速度非常快,因此内存缓存可以快速地提供已加载图片的显示。
Glide中的内存缓存起点可以直接从以下代码开始:
ini
// 内存缓存区
private final MemoryCache cache;
private EngineResource<?> loadFromCache(Key key) {
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
// 从内存缓存中获取到resource时就将其移至活动缓存中
activeResources.activate(key, cached);
}
return cached;
}
private EngineResource<?> getEngineResourceFromCache(Key key) {
// 从内存缓存中获取到Resource时就将其从内存缓存移除
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;
}
对于获取内存缓存中的资源,有一点需要说明:
从内存缓存中取到值后,就要将其从内存缓存中移除,放到活动缓存中,因为活动缓存通常用于正在展示的图片,而获取图片资源通常意味着要展示,因此,从内存缓存中获取到的值要移至活动缓存中也顺理成章,也是多级缓存管理的一种常见思路。
内存缓存的内部实现
从上文我们可以看到,内存缓存对应的类是MemoryCache
,MemoryCache
有两个实现类:
MemoryCacheAdapter
LruResourceCache
其中, MemoryCacheAdapter
是一个适配器类,它并不是一个真正的内存缓存实现,而是为了兼容一些特殊场景而存在的。如果你在Glide的配置中选择不使用内存缓存,或者自己实现了自定义的内存缓存机制,但仍然想使用Glide的图片加载功能,就可以选择使用MemoryCacheAdapter
。这个适配器会简单地将图片资源保存在一个内部的Map中,但不执行真正的内存缓存策略。MemoryCacheAdapter
通常用于关闭内存缓存时的临时替代。
因此,真正实现内存缓存的类是LruResourceCache
,从名字我们可以看出,这个类主要采用了LRU算法来进行缓存的存和取。
LRU(Least Recently Used) 算法是一种常见的缓存淘汰策略,LRU算法基于以下原则:当缓存满时,应该淘汰最久未被使用的缓存项,从而保留最近使用的缓存项,以期在未来的访问中仍然能够被频繁访问到。
内存缓存中的LRU算法实现
在真正开始了解LRU算法的实现前,我们看一下LRU存和取的过程:
暂时无法在飞书文档外展示此内容
LRU算法的原理如上图所示,主要包含两个关键点:
-
每次被操作(添加或者获取)的元素都会放到列表的头部;
-
内存满后,再添加元素时,就会删除列表的尾部元素
因此,我们再来看LruResourceCache
中的实现,LruResourceCache
继承自LruCache
,即真正LRU
算法的实现在LruCache
类中:
java
public class LruCache<T, Y> {
// 1. 使用LinkedHashMap作为存取的数据结构
private final Map<T, Entry<Y>> cache = new LinkedHashMap<>(100, 0.75f, true);
private final long initialMaxSize;
private long maxSize;
private long currentSize;
/**
* Constructor for LruCache.
*
* @param size The maximum size of the cache, the units must match the units used in {@link
* #getSize(Object)}.
*/
public LruCache(long size) {
this.initialMaxSize = size;
this.maxSize = size;
}
/**
* Returns the item in the cache for the given key or null if no such item exists.
*
* @param key The key to check.
*/
// 2. 获取元素
@Nullable
public synchronized Y get(@NonNull T key) {
Entry<Y> entry = cache.get(key);
return entry != null ? entry.value : null;
}
// 3. 添加元素
@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 Entry<Y> old = cache.put(key, item == null ? null : new Entry<>(item, itemSize));
if (old != null) {
currentSize -= old.size;
if (!old.value.equals(item)) {
onItemEvicted(key, old.value);
}
}
evict();
return old != null ? old.value : null;
}
@Nullable
public synchronized Y remove(@NonNull T key) {
Entry<Y> entry = cache.remove(key);
if (entry == null) {
return null;
}
currentSize -= entry.size;
return entry.value;
}
/** Clears all items in the cache. */
public void clearMemory() {
trimToSize(0);
}
/**
* Removes the least recently used items from the cache until the current size is less than the
* given size.
*
* @param size The size the cache should be less than.
*/
protected synchronized void trimToSize(long size) {
Map.Entry<T, Entry<Y>> last;
Iterator<Map.Entry<T, Entry<Y>>> cacheIterator;
while (currentSize > size) {
cacheIterator = cache.entrySet().iterator();
last = cacheIterator.next();
final Entry<Y> toRemove = last.getValue();
currentSize -= toRemove.size;
final T key = last.getKey();
cacheIterator.remove();
onItemEvicted(key, toRemove.value);
}
}
private void evict() {
trimToSize(maxSize);
}
@Synthetic
static final class Entry<Y> {
final Y value;
final int size;
@Synthetic
Entry(Y value, int size) {
this.value = value;
this.size = size;
}
}
}
为了更清晰的了解代码实现,在以上代码中,我删除了部分代码。
-
LruCache
中用了一个LinkedHashMap
作为缓存存取的数据结构-
LinkedHashMap
继承自HashMap
,并且单独维护了一个双向链表,这样就可以在操作元素时,算法复杂度仅为O(1) -
构造函数中
accessOrder
参数为true
, 这样最新访问的就会放入队尾。
-
-
获取元素
LruCache
中的get
方法直接调用LinkedHashMap.get
:
ini
public V get(Object key) {
Node<K,V> e;
//
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMapEntry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMapEntry<K,V> p =
(LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
在获取到指定元素后,由于accessOrder
为true
,因此会调用afterNodeAccess
方法,将获取到的元素放到链表的尾部,LinkedHashMap
中最近使用的元素会被放到队尾。
- 添加元素
less
public synchronized Y put(@NonNull T key, @Nullable Y item) {
final int itemSize = getSize(item);
// 判断itemSize是否大于缓存的最大size
if (itemSize >= maxSize) {
onItemEvicted(key, item);
return null;
}
if (item != null) {
currentSize += itemSize;
}
// 1.调用LinkedHashMap.put
@Nullable Entry<Y> old = cache.put(key, item == null ? null : new Entry<>(item, itemSize));
if (old != null) {
// 如果之前的key对应的value不为空,则将currentSize减1(默认itemSize为1)
currentSize -= old.size;
if (!old.value.equals(item)) {
onItemEvicted(key, old.value);
}
}
// 2.如果缓存满了,就删除最近未使用的元素
evict();
return old != null ? old.value : null;
}
- 调用
LinkedHashMap.put
,LinkedHashMap
并未重写put
方法,因此还是调用的HashMap.put
,但是LinkedHashMap
重写了newNode
与afterNodeAccess
方法:
ini
// HashMap
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
// 之前不存在对应Hash值的元素,调用newNode
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
// 存在key对应的元素后调用afterNodeAccess
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
可以看到,不管是什么情况,HashMap.put
方法都会调用newNode
方法或者afterNodeAccess
方法:
ini
// LinkedHashMap
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMapEntry<K,V> p =
new LinkedHashMapEntry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
}
// link at the end of list
private void linkNodeLast(LinkedHashMapEntry<K,V> p) {
LinkedHashMapEntry<K,V> last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
LinkedHashMap.newNode
方法中会将新元素放到队尾,而afterNodeAccess
方法上文已经讲过,也是将指定元素放到队尾。
evict
方法
ini
private void evict() {
trimToSize(maxSize);
}
protected synchronized void trimToSize(long size) {
Map.Entry<T, Entry<Y>> last;
Iterator<Map.Entry<T, Entry<Y>>> cacheIterator;
while (currentSize > size) {
cacheIterator = cache.entrySet().iterator();
last = cacheIterator.next();
final Entry<Y> toRemove = last.getValue();
currentSize -= toRemove.size;
final T key = last.getKey();
cacheIterator.remove();
onItemEvicted(key, toRemove.value);
}
}
当前链表size大于最大容量时,删除队头的元素
磁盘缓存
当我们从活动缓存以及内存缓存中都没有获取到指定资源时,我们就需要从磁盘缓存或者网络来获取:
该部分的起点是waitForExistingOrStartNewJob
方法:
scss
private <R> LoadStatus waitForExistingOrStartNewJob(
GlideContext glideContext,
...
ResourceCallback cb,
Executor callbackExecutor,
EngineKey key,
long startTime) {
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);
}
EngineJob<R> engineJob =
engineJobFactory.build(
key,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache);
DecodeJob<R> decodeJob =
decodeJobFactory.build(
glideContext,
...
engineJob);
jobs.put(key, engineJob);
engineJob.addCallback(cb, callbackExecutor);
engineJob.start(decodeJob);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}
该方法中创建了两个对象:EngineJob
和DecodeJob
,
-
EngineJob
用于开启任务,在资源加载成功和失败时回调; -
DecodeJob
对象用于加载资源,操作缓存等 -
此处
EngineJob.start()
内部就是调用线程池的excute()
方法,传入的正是DecodeJob
,因此接着进入DecodeJob.run()
方法。
DecodeJob.runWrapped()
csharp
@Override
public void run() {
DataFetcher<?> localFetcher = currentFetcher;
try {
if (isCancelled) {
notifyFailed();
return;
}
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;
default:
throw new IllegalStateException("Unrecognized run reason: " + runReason);
}
}
runWrapped
方法中根据runReason
的不同状态值执行不同的逻辑:
-
INITIALIZE
:初始化状态; -
SWITCH_TO_SOURCE_SERVICE
:从磁盘服务切换到远程(网络)服务; -
DECODE_DATA
:由于格式或其他原因,仍然需要对图像数据进行解码。在这种情况下,DecodeJob
会以DECODE_DATA
状态运行
当状态为INITIALIZE
时,我们通过getNextStage
获取stage
值:
arduino
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);
}
}
Stage
也有不同的值:
RESOURCE_CACHE
:已解码的资源缓存阶段;DATA_CACHE
:图片源文件缓存状态;SOURCE
:网络请求状态FINISHED
:结束状态
而不同的取值一是取决于传入的Stage
值以及DiskCacheStrategy
,DiskCacheStrategy
有几种类型:
在Glide图像加载库中,DiskCacheStrategy
枚举类定义了不同的磁盘缓存策略,用于控制图像在磁盘缓存中的存储行为。以下是 DiskCacheStrategy
可用的几种类型:
ALL
:
此策略表示将原始图像数据和已解码的图像资源都存储在磁盘缓存中。无论图像是否被解码,都会存储。这意味着即使图像已被解码,其原始数据也会被存储在磁盘上,以便后续使用。
NONE
:
NONE
策略表示不将任何图像数据存储在磁盘缓存中。这适用于那些你不希望在磁盘上保留图像的情况,例如临时的或敏感的图像。
DATA
:
DATA
策略表示只将原始图像数据存储在磁盘缓存中,而不存储已解码的图像资源。这适用于只想存储原始数据以进行后续处理或转发的情况。
RESOURCE
:
RESOURCE
策略表示只将已解码的图像资源存储在磁盘缓存中,而不存储原始图像数据。这适用于希望在磁盘上保留解码后的图像资源以供重用的情况。
AUTOMATIC
:
AUTOMATIC
策略会根据图像的特性和请求来自动决定是否存储图像数据在磁盘缓存中。通常,它会存储原始数据和解码后的资源,以便在需要时进行快速访问。
通常选择**AUTOMATIC
**即可,因此diskCacheStrategy.decodeCachedResource()
为true
,因此,我们再回到runWrapped
函数中:
csharp
private void runWrapped() {
switch (runReason) {
case INITIALIZE:
// stage为RESOURCE_CACHE
stage = getNextStage(Stage.INITIALIZE);
currentGenerator = getNextGenerator();
runGenerators();
break;
...
}
}
stage
为RESOURCE_CACHE
后,调用getNextGenerator
:
csharp
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);
}
}
该函数根据不同的stage值,返回不同的DataFetcherGenerator
对象,此处返回ResourceCacheGenerator
对象,接着调用runGenerators
方法
DecodeJob.runGenerators
ini
private void runGenerators() {
currentThread = Thread.currentThread();
startFetchTime = LogTime.getLogTime();
boolean isStarted = false;
//
while (!isCancelled
&& currentGenerator != null
// 重点1
&& !(isStarted = currentGenerator.startNext())) {
stage = getNextStage(stage);
currentGenerator = getNextGenerator();
// 重点2
if (stage == Stage.SOURCE) {
reschedule(RunReason.SWITCH_TO_SOURCE_SERVICE);
return;
}
}
// We've run out of stages and generators, give up.
if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
notifyFailed();
}
// Otherwise a generator started a new load and we expect to be called back in
// onDataFetcherReady.
}
runGenerators
函数中有一个while循环,有两种情况可以结束循环:
-
currentGenerator.startNext()
返回true
:表明currentGenerator
从磁盘加载数据成功currentGenerator
可以是ResourceCacheGenerator
或者DataCacheGenerator
,前者表明存在指定资源缓存,后者表明存在源数据缓存
-
stage == Stage.SOURCE
:表明从磁盘加载数据失败,需要进入下一步--从网络获取资源
当stage == Stage.SOURCE
时,调用reschedule(RunReason.SWITCH_TO_SOURCE_SERVICE)
:
typescript
private void reschedule(RunReason runReason) {
this.runReason = runReason;
callback.reschedule(this);
}
callback
就是EngineJob
对象:
scss
public void reschedule(DecodeJob<?> job) {
// Even if the job is cancelled here, it still needs to be scheduled so that it can clean itself
// up.
getActiveSourceExecutor().execute(job);
}
获取SourceExecutor
,继续执行DecodeJob
,也就接着执行runWrapped
方法:
紧接着走SourceGenerator.runGenerators
方法获取网络图片,获取到资源后写入磁盘缓存。
可以看到,整体的获取磁盘缓存或网络图片的流程稍微有些绕,一不小心就绕迷糊了,
小结
-
先从
ResourceCacheGenerator
中去寻找是否有经过转换之后的磁盘文件缓存,这个缓存要比源文件缓存小; -
如果上一步没有获得数据,再从
DataSourceGenerator
中寻找源文件磁盘缓存,这个是加载的源文件直接写入缓存; -
如果都没命中,那么就去
Http
请求资源,资源请求回来之后先写入磁盘缓存,再返回到Target
。
总结
Glide
三级缓存涉及到活动缓存/内存缓存/磁盘缓存;- 活动缓存采用弱引用的方式存储资源,也是一种内存缓存,用于缓存正在使用的资源;
- 内存缓存采用LRU算法存储资源;
- 磁盘缓存分为指定资源缓存与指定资源对应的解码后原文件缓存