本篇文章主要来梳理Glide的缓存策略。
先给出客户端配置举例:
Glide.with(this)
.load(Constants.dynamicWebpUrl)
.apply(new RequestOptions()
.skipMemoryCache(false)//运行内存缓存
.diskCacheStrategy(DiskCacheStrategy.ALL)) //配置缓存策略
.into(mImageView);
这个配置包含了内存缓存、磁盘缓存(原始数据和转换后数据)。
整个Glide图片获取的方式有三种:内存缓存、 磁盘缓存 、网络请求。
一、内存缓存
从Engine load方法作为入口开始:
Engine.java
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) {
…
//通过多个参数最终生成一个唯一资源标识的缓存key对象EngineKey
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
//先获取loadFromActiveResources
EngineResource> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
...
}
//再获取loadFromCache
EngineResource> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
...
}
//最后获取EngineJob,然后走engineJob.start
EngineJob> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb);
...
}
...
jobs.put(key, engineJob);
engineJob.addCallback(cb);
engineJob.start(decodeJob);
...
return new LoadStatus(cb, engineJob);
}
一个个来看:
1.1 loadFromActiveResources 活跃资源内存缓存
Engine.java
private final ActiveResources activeResources;//在Engine构造方法中初始化
private EngineResource> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
//这就是通过RequestOptions配置的skipMemoryCache,但isMemoryCacheable与skipMemoryCache是相反的
if (!isMemoryCacheable) {
return null;
}
EngineResource> active = activeResources.get(key);
if (active != null) {
active.acquire();
}
return active;
}
ActiveResources.java
final Map activeEngineResources = new HashMap<>();//弱引用缓存
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.java
void activate(Key key, EngineResource> resource) {
ResourceWeakReference toPut =
new ResourceWeakReference(
key,
resource,
getReferenceQueue(),
isActiveResourceRetentionAllowed);
ResourceWeakReference removed = activeEngineResources.put(key, toPut);//该方法对应了map唯一put的地方
if (removed != null) {
removed.reset();
}
}
调用点:从loadFromCache成功获取、通过EngineJob从磁盘或者网络成功获取。
也就是说从其他途径成功获取到当前正在加载的图片资源,则会缓存到活跃内存缓存,形式是弱引用对象。如果loadFromActiveResources能成功获取,则直接返回。
1.2 loadFromCache
loadFromActiveResources获取为null,则尝试loadFromCache
Engine.java
private EngineResource> loadFromCache(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
EngineResource> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
activeResources.activate(key, cached);
}
return cached;
}
Engine.java
private EngineResource> getEngineResourceFromCache(Key key) {
//cache是 LruResourceCache extends LruCache
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, true /*isMemoryCacheable*/, true /*isRecyclable*/);
}
return result;
}
那在什么时候缓存的?
Engine.java
public void onResourceReleased(Key cacheKey, EngineResource> resource) {
Util.assertMainThread();
activeResources.deactivate(cacheKey);
if (resource.isCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
调用点:active弱引用被回收会转到cache、EngineResource调用release的时候。
总结下结论:
取的逻辑:加载图片先从activeResources取,没有再去MemoryCache取,再没有就起EngineJob线程走磁盘或者网络获取。
存的逻辑:当前加载到ImageView的图片会先被缓存到activeResources,activeResources被remove的会被加入到MemoryCache。
二、磁盘缓存
内存获取不到会起个线程即DecodeJob来完成接下来的任务。
DecodeJob.java
public void run() {
...
runWrapped();
...
}
private void runWrapped() {
switch (runReason) {
case INITIALIZE: //首次进入走INITIALIZE
stage = getNextStage(Stage.INITIALIZE);//1
currentGenerator = getNextGenerator();//2
runGenerators();//3
break;
case SWITCH_TO_SOURCE_SERVICE:
runGenerators();
break;
case DECODE_DATA:
decodeFromRetrievedData();
break;
default:
throw new IllegalStateException("Unrecognized run reason: " + runReason);
}
}
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:
- INITIALIZE //初始阶段
- RESOURCE_CACHE //从磁盘缓存中读取转换之后的图片
- DATA_CACHE 从磁盘缓存中读取原始图片
- SOURCE //从网络上加载
- ENCODE //在成功加载后对转换的资源进行编码
- FINISHED //完成阶段
diskCacheStrategy:
ALL 表示既缓存原始图片,也缓存转换过后的图片。
* isDataCacheable return dataSource == DataSource.REMOTE;
* isResourceCacheable return dataSource != DataSource.RESOURCE_DISK_CACHE && dataSource != DataSource.MEMORY_CACHE;
* decodeCachedResource return true
* decodeCachedData return true
NONE 表示不缓存任何内容。
* isDataCacheable return false;
* isResourceCacheable return false;
* decodeCachedResource return false;
* decodeCachedData return false;
DATA 表示只缓存原始图片。
* isDataCacheable dataSource != DataSource.DATA_DISK_CACHE && dataSource != DataSource.MEMORY_CACHE;
* isResourceCacheable return false;
* decodeCachedResource return false;
* decodeCachedData return true;
RESOURCE 表示只缓存转换过后的图片。
* isDataCacheable return false;
* isResourceCacheable return dataSource != DataSource.RESOURCE_DISK_CACHE && dataSource != DataSource.MEMORY_CACHE;
* decodeCachedResource return true;
* decodeCachedData return false;
AUTOMATIC 表示让Glide根据图片资源智能地选择使用哪一种缓存策略(默认选项)。
* isDataCacheable return dataSource == DataSource.REMOTE;
* isResourceCacheable return ((isFromAlternateCacheKey && dataSource == DataSource.DATA_DISK_CACHE)
|| dataSource == DataSource.LOCAL)
&& encodeStrategy == EncodeStrategy.TRANSFORMED;
* decodeCachedResource return true;
* decodeCachedData return true;
根据getNextStage去匹配到对应的stage,然后根据stage匹配对应的Generator,而generator是具体处理类。
interface DataFetcherGenerator
实现类:
- ResourceCacheGenerator 原始数据转换后的缓存
- DataCacheGenerator 原始数据缓存
- SourceGenerator 新资源获取
理论上整个获取顺序就是:ResourceCache > DataCache > Source,当然也可以自定义diskCacheStrategy来调整。
但是这里需要研究一下默认的DiskCacheStrategy.AUTOMATIC到底是怎么个智能法?
Glide.with(mContext) .load(Constants.staticWebpUrl).into(mImageView);
这种使用方式,默认就是AUTOMATIC。且skipMemoryCache也默认是false,也就是允许内存缓存。
首次加载图片:
最终为false.
有磁盘缓存之后第二次加载图片:
最终也为false.
因此:AUTOMATIC本身可以理解为并不支持转换后的资源缓存。
三、网络请求
磁盘缓存中转换资源和原始资源都获取不到,则走网络请求获取。
SourceGenerator.java
public boolean startNext() {
...
while (!started && hasNextModelLoader()) {
loadData = helper.getLoadData().get(loadDataListIndex++);
if (loadData != null
&& (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
|| helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
started = true;
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
这里举例:OkHttpStreamFetcher
public void loadData(@NonNull Priority priority,
@NonNull final DataCallback super InputStream> callback) {
Request request = null;
if (url != null) {
Request.Builder requestBuilder = new Request.Builder().url(url.toStringUrl());
if (url.getHeaders() != null) {
for (Map.Entry headerEntry : url.getHeaders().entrySet()) {
if (headerEntry != null) {
String key = headerEntry.getKey();
requestBuilder.addHeader(key, headerEntry.getValue());
}
}
request = requestBuilder.build();
}
}
this.callback = callback;
if (client != null && request != null) {
call = client.newCall(request);
if (call != null) {
call.enqueue(this);
}
}
}
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "OkHttp failed to obtain result", e);
}
if (callback != null) {
callback.onLoadFailed(e);
}
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
if (response == null || response.body() == null) {
return;
}
Log.d(TAG, "ImageLoader- okhttp onResponse timeMs:" + response.getNetworkTimeMs());
responseBody = response.body();
if (response.isSuccessful()) {
long contentLength = responseBody.contentLength();
stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);
if (callback != null) {
callback.onDataReady(stream);
}
} else {
if (callback != null) {
callback.onLoadFailed(new HttpException(response.message(), response.code()));
}
}
}
这里网络请求成功才会回调callback.onDataReady(stream),stream是数据流,callback是SourceGenerator
SourceGenerator.java
public void onDataReady(Object data) {
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
//这里满足diskCacheStrategy.isDataCacheable开始走缓存流程
if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
dataToCache = data;
// We might be being called back on someone else's thread. Before doing anything, we should
// reschedule to get back onto Glide's thread.
cb.reschedule();
} else {
cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
loadData.fetcher.getDataSource(), originalKey);
}
}
cb.reschedule()这里cb是DecodeJob
DecodeJob.java
public void reschedule() {
runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
callback.reschedule(this);
}
这个callback是EngineJob
DecodeJob.java
@Override
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);
}
这里绕了一圈又回到DecodeJob的run方法,这次runReason为SWITCH_TO_SOURCE_SERVICE,DecodeJob执行reschedule()时赋值的
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);
}
}
走SWITCH_TO_SOURCE_SERVICE,对应 runGenerators();
debug一下,发现这个generator仍然是SourceGenerator
这次进来是走的cache了
private void cacheData(Object dataToCache) {
GlideUtils.getCallers(40);
long startTime = LogTime.getLogTime();
try {
Encoder
最终由cacheData方法完成磁盘缓存。
最后总结一张缓存逻辑图:
这里简单总结下:
取逻辑:
内存 > 磁盘 > 网络请求
内存:
上次刚被加载的资源(activeResources) > (最近被加载的资源)lruCache。磁盘:
如果有主动设置DiskCacheStrategy,则按设置来。默认是取转换之后的资源(ResourceCache) > (DataCache)原始资源网络请求:
走网络请求获取图片资源流。
存逻辑
内存:
当前被加载的图片资源存到activeResources中,下次加载资源切换,当前activeResources会remove然后被转移到lruCache。磁盘:
网络请求成功之后,获取到资源流,然后看DiskCacheStrategy是否支持磁盘缓存,如果支持,会通过回调在SourceGenerator中通过cacheData进行资源缓存。