Glide中的缓存策略

先来看看LinkedHashMap

1.Glide的LruCache基于LinkedHashMap实现,最近最少使用,我们需要关心的是如何对数据进行保存删除,并按照这个策略实现存储的。
2.LinkedHashMap中几个重要方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* The head (eldest) of the doubly linked list.
*/
//这里定义了头部节点,按照注释即为最不常用的
transient LinkedHashMapEntry<K,V> head;

/**
* The tail (youngest) of the doubly linked list.
*/
//同样的尾节点,即最近使用的
transient LinkedHashMapEntry<K,V> tail;
//是否排序,默认是false的
final boolean accessOrder;

3.回到Glide的使用场景,当我们在使用一张图片时,cache.get(key),通过这个get操作来看看LinkedHashMap对数据做了什么改变。
4.LinkedHashMap中的get方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
//LruCache中accessOrder是为true的
if (accessOrder)
afterNodeAccess(e);
return e.value;
}

// move node to last将数据node移动到最后,看看是怎样实现的
void afterNodeAccess(Node<K,V> e) {
LinkedHashMapEntry<K,V> last;
//这里对被需要取的数据做了一次判断,判断元素e是否为tail节点,因为tail节点我们是知道的,可以直接获得
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;
}
}

当被待取的元素为头节点(链表中四个元素)

1.定义了变量p接收待取元素e,主要就是节点的打开与重链。假设待取元素p=head,b=p.before =head.before = null; a=p.after = head.after,将p.after置为空(元素被使用即将元素移动到队尾,也即为tail,而tail.after = null)。
2.因为b=null,将头节点改为a->head = a;(a为第二个元素),既然a变为新的头节点,a.before = b = null;
3.p.before = last;(last为tail),last.after=p,到此p被成功移动到队尾作为新的tail节点.
Y0nZ2d.md.png

当被待取元素为中间节点

1.移动的操作更加简单,只是加待取的节点的after置空,并将before节点指向原来的tail节点;置空之前拿到待取节点前后两个元素,将这两个元素进行联系。

Glide中的LruCache

1.方法很简单,直接看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class LruCache<T, Y> {
//默认的容量为100,并开启了accessOrder=true,加载因子
private final Map<T, Entry<Y>> cache = new LinkedHashMap<>(100, 0.75f, true);
//初始化构造方法中的传入size
private final long initialMaxSize;
private long maxSize;
//当前size
private long currentSize;

public LruCache(long size) {
this.initialMaxSize = size;
this.maxSize = size;
}

//这个方法需要子类实现,glide中既然保存的是图片,得到的即为当张图片的大小,bytes[]数组
protected int getSize(@Nullable Y item) {
return 1;
}

/**
* A callback called whenever an item is evicted from the cache. Subclasses can override.
*
* @param key The key of the evicted item.
* @param item The evicted item.
*/
//cache中删除较老的item时会回调这个方法,子类需要重写
protected void onItemEvicted(@NonNull T key, @Nullable Y item) {
// optional override
}
}

2.添加元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//既然用户缓存图片,那么以图片为例
public synchronized Y put(@NonNull T key, @Nullable Y item) {
//获得待添加图片的大小bytes[]
final int itemSize = getSize(item);
//比最大容量还要大,显然是不能添加进去
if (itemSize >= maxSize) {
onItemEvicted(key, item);
return null;
}
//currentSize = currentSize + itemSize,更新了当前已存size自增更新(此时还是小于阀值的)
if (item != null) {
currentSize += itemSize;
}

@Nullable Entry<Y> old = cache.put(key, item == null ? null : new Entry<>(item, itemSize));
if (old != null) {
//添加此张图片到caChe中,返回一张(old!=null),也就是说待添加的图片对应的key已经存在了,那么要做的就是更新key对应的值value
//由于添加之前做了自增操作,由于是覆盖了原始值,那么这里要减去old value的大小
currentSize -= old.size;

if (!old.value.equals(item)) {
onItemEvicted(key, old.value);
}
}
evict();
return old != null ? old.value : null;
}

private void evict() {
trimToSize(maxSize);
}
/**
* 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) {
//这个命名不联系注释,很多人以为是最后一个数据,网上很多这种说法,很明显这种说法是错误的
//这里的last相当于最后被使用的意思(最近最少使用),注释也说明了Removes the least recently used,并且是从头部节点开始做删除操作
//cacheIterator = cache.entrySet().iterator(); last = cacheIterator.next();
Map.Entry<T, Entry<Y>> last;
Iterator<Map.Entry<T, Entry<Y>>> cacheIterator;
//对容量的控制,直到当前的容量小于最大值maxSize为止
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);
}
}

3.Glide中的LruCache还是比较简单的,总结:增加数据时,判断待添加的元素大小与maxSzie作对比,maxSize作为容量的阀值,当currentSize大于阀值时,作删除操作(从头部节点开始)。对于元素的操作保留了空方法onItemEvicted用于子类实现相应的逻辑。

Glide缓存原理

1.先来看看Glide的使用代码:

1
2
3
4
5
Glide.with(this)
.load(url)
.skipMemoryCache(false)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.into(target);

2.请求相关的几个关键类:RequestManager、RequestBuilder,SingleRequest、Engine、EngineJob。

RequestManager

1.请求的管理,包括生命周期的管理,官方注释:

1
2
3
4
5
6
7
/**A class for managing and starting requests for Glide. Can use activity, fragment and connectivity
*lifecycle events to intelligently stop, start, and restart requests. Retrieve either by
*instantiating a new object, or to take advantage built in Activity and Fragment lifecycle
*handling, use the static Glide.load methods with your Fragment or Activity.
*/
//用于管理和启动对Glide的请求类,context可以是Activity、Fragment,具有生命周期感知,停止,开始,重新请求等。
`

2.注释解释的很清楚,也就是一个具有跟我们Activity、Fragment生命周期绑定的请求管理类。当我们的Activity切换到后台,显然是要停止一些请求操作的,而RequestManager主要负责这些工作。
3.并且RequestManager包含load以及其重载的方法,当调用传入URL,其实就是将传入的参数URL通过Builder构建了一个RequestBuilder:

1
2
3
4
5
6
7
8
9
10
11
/**
* Equivalent to calling {@link #asDrawable()} and then {@link RequestBuilder#load(String)}.
*
* @return A new request builder for loading a {@link Drawable} using the given model.
*/
@NonNull
@CheckResult
@Override
public RequestBuilder<Drawable> load(@Nullable String string) {
return asDrawable().load(string);
}

RequestBuilder

1.资源的构建类,根据不同的资源类型进行一些基础设置:

A generic class that can handle setting options and staring loads for generic resource types.

2.关键重载的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
* Set the target the resource will be loaded into.
*
* @param target The target to load the resource into.
* @return The given target.
* @see RequestManager#clear(Target)
*/
@NonNull
public <Y extends Target<TranscodeType>> Y into(@NonNull Y target) {
return into(target, /*targetListener=*/ null, Executors.mainThreadExecutor());
}

@NonNull
@Synthetic
<Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
Executor callbackExecutor) {
return into(target, targetListener, /*options=*/ this, callbackExecutor);
}

private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
BaseRequestOptions<?> options,
Executor callbackExecutor) {
Preconditions.checkNotNull(target);
if (!isModelSet) {
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
//Request是一个接口,SingleRequest实现了Request,由此,请求被转换到SingleRequest中。
Request request = buildRequest(target, targetListener, options, callbackExecutor);
Log.d("RequestBuilder", "----->into");
Request previous = target.getRequest();
if (request.isEquivalentTo(previous)
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
// If the request is completed, beginning again will ensure the result is re-delivered,
// triggering RequestListeners and Targets. If the request is failed, beginning again will
// restart the request, giving it another chance to complete. If the request is already
// running, we can let it continue running without interruption.
if (!Preconditions.checkNotNull(previous).isRunning()) {
// Use the previous request rather than the new one to allow for optimizations like skipping
// setting placeholders, tracking and un-tracking Targets, and obtaining View dimensions
// that are done in the individual Request.
previous.begin();
Log.d("RequestBuilder---->", "previous->" + previous.toString());
}
return target;
}

requestManager.clear(target);
target.setRequest(request);
requestManager.track(target, request);

return target;
}

3.真正请求的:

Request request = buildRequest(target, targetListener, options, callbackExecutor);

SingleRequest

1.关键方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
@Override
public void begin() {
Log.d("SingleRequest", "--->begin");
synchronized (requestLock) {
assertNotCallingCallbacks();
stateVerifier.throwIfRecycled();
startTime = LogTime.getLogTime();
if (model == null) {
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
width = overrideWidth;
height = overrideHeight;
}
// Only log at more verbose log levels if the user has set a fallback drawable, because
// fallback Drawables indicate the user expects null models occasionally.
int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
onLoadFailed(new GlideException("Received null model"), logLevel);
return;
}
if (status == Status.RUNNING) {
throw new IllegalArgumentException("Cannot restart a running request");
}
// If we're restarted after we're complete (usually via something like a notifyDataSetChanged
// that starts an identical request into the same Target or View), we can simply use the
// resource and size we retrieved the last time around and skip obtaining a new size, starting
// a new load etc. This does mean that users who want to restart a load because they expect
// that the view size has changed will need to explicitly clear the View or Target before
// starting the new load.
if (status == Status.COMPLETE) {
onResourceReady(resource, DataSource.MEMORY_CACHE);
return;
}
// Restarts for requests that are neither complete nor running can be treated as new requests
// and can run again from the beginning.
status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
target.getSize(this);
}
if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
&& canNotifyStatusChanged()) {
target.onLoadStarted(getPlaceholderDrawable());
}
if (IS_VERBOSE_LOGGABLE) {
logV("finished run method in " + LogTime.getElapsedMillis(startTime));
}
}
}

/** A callback method that should never be invoked directly. */
@Override
public void onSizeReady(int width, int height) {
stateVerifier.throwIfRecycled();
synchronized (requestLock) {
if (IS_VERBOSE_LOGGABLE) {
logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
}
if (status != Status.WAITING_FOR_SIZE) {
return;
}
status = Status.RUNNING;

float sizeMultiplier = requestOptions.getSizeMultiplier();
this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
this.height = maybeApplySizeMultiplier(height, sizeMultiplier);

if (IS_VERBOSE_LOGGABLE) {
logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
}
loadStatus =
engine.load(
glideContext,
model,
requestOptions.getSignature(),
this.width,
this.height,
requestOptions.getResourceClass(),
transcodeClass,
priority,
requestOptions.getDiskCacheStrategy(),
requestOptions.getTransformations(),
requestOptions.isTransformationRequired(),
requestOptions.isScaleOnlyOrNoTransform(),
requestOptions.getOptions(),
requestOptions.isMemoryCacheable(),
requestOptions.getUseUnlimitedSourceGeneratorsPool(),
requestOptions.getUseAnimationPool(),
requestOptions.getOnlyRetrieveFromCache(),
this,
callbackExecutor);

// This is a hack that's only useful for testing right now where loads complete synchronously
// even though under any executor running on any thread but the main thread, the load would
// have completed asynchronously.
if (status != Status.RUNNING) {
loadStatus = null;
}
if (IS_VERBOSE_LOGGABLE) {
logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
}
}
}

Tips

1.整个load流程还是比价清新明了的,各个模块之间配合,通过RequestManager统一对请求的生命周期统一管理,RequestBuilder对请求做一些基础设置,而真正的请求则由SingleRequest去执行。下面看看对结果的处理。

Engine、EngineJob

1.重点看load方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
  省略部分代码
1.Check the current set of actively used resources, return the active resource if present,and move any newly inactive resources *into the memory cache.
2.Check the memory cache and provide the cached resource if present.
3.Check the current set of in progress loads and add the cb to the in progress load if one is present.

public <R> LoadStatus load(args...){
synchronized (this) {
//step1
memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);

if (memoryResource == null) {
Log.d("Engine", "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);
}
}

@Nullable
private EngineResource<?> loadFromMemory(
EngineKey key, boolean isMemoryCacheable, long startTime) {
if (!isMemoryCacheable) {
return null;
}
//step2
EngineResource<?> active = loadFromActiveResources(key);
if (active != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return active;
}

@Nullable
private EngineResource<?> loadFromActiveResources(Key key) {
EngineResource<?> active = activeResources.get(key);
if (active != null) {
active.acquire();
}
return active;
}

//step3
EngineResource<?> cached = loadFromCache(key);
if (cached != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return cached;
}
return null;
}

private EngineResource<?> loadFromCache(Key key) {
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
Log.d("Engine", "loadFromCache--->cache中获取");
activeResources.activate(key, cached);
}
return cached;
}
}

2.逻辑还是很清晰的loadFromMemory内部,首先查找的是ActiveResources,可以理解为活动缓存,当加载一张图片时,首先从活动缓存中获取,如果命中则直接返回。并且引用计数自增操作。
3.如果活动缓存中没有命中接着去内存缓存中memory中获取,同样的引用计数会自增。同时调用了active方法,内部维护了一个引用队列,和一个弱引用的resource,map结合。

1
2
3
4
5
6
7
8
9
10
11
12
synchronized void activate(Key key, EngineResource<?> resource) {
ResourceWeakReference toPut =
new ResourceWeakReference(
key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);

//这里对相同key对应的value进行了覆盖更新操作。
ResourceWeakReference removed = activeEngineResources.put(key, toPut);
if (removed != null) {
//释放
removed.reset();
}
}

4.如果都未命中,memoryResource == null,在waitForExistingOrStartNewJob中,首先会尝试从disk中获取,仍然没有最后才会执行网络下载。

这个功能是摆设,看看就好~~~