本篇文章主要来梳理Glide的缓存策略。
先给出客户端配置举例:
Glide.with(this)
.load(Constants.dynamicWebpUrl)
.apply(new RequestOptions()
.skipMemoryCache(false)//运行内存缓存
.diskCacheStrategy(DiskCacheStrategy.ALL)) //配置缓存策略
.into(mImageView);
这个配置包含了内存缓存、磁盘缓存(原始数据和转换后数据)。
整个Glide图片获取的方式有三种:内存缓存、 磁盘缓存 、网络请求。
一、内存缓存
从Engine load方法作为入口开始:
Engine.java
public <R> LoadStatus load(
GlideContext glideContext,
Object model,
Key signature,
int width,
int height,
Class<?> resourceClass,
Class<R> transcodeClass,
Priority priority,
DiskCacheStrategy diskCacheStrategy,
Map<Class<?>, 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<Key, ResourceWeakReference> 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 //缓存原始数据和转换后的数据
- NONE //不缓存
- DATA //原始数据,未经过解码或者转换
- RESOURCE //缓存经过解码的数据
- AUTOMATIC //根据EncodeStrategy和DataSource等条件自动选择合适的缓存方式,
这里不同的diskCacheStrategy有不同的策略:比如:
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; //支持原始资源解码
}
};
根据getNextStage去匹配到对应的stage,然后根据stage匹配对应的Generator,而generator是具体处理类。
interface DataFetcherGenerator
实现类:
- ResourceCacheGenerator 原始数据转换后的缓存
- DataCacheGenerator 原始数据缓存
- SourceGenerator 新资源获取
默认整个获取顺序就是:ResourceCache > DataCache > Source,当然也可以自定义diskCacheStrategy来调整。
三、网络请求
磁盘缓存中转换资源和原始资源都获取不到,则走网络请求获取。
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<String, String> 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<Object> encoder = helper.getSourceEncoder(dataToCache);
DataCacheWriter<Object> writer =
new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
//磁盘缓存写入
helper.getDiskCache().put(originalKey, writer);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Finished encoding source to cache"
+ ", key: " + originalKey
+ ", data: " + dataToCache
+ ", encoder: " + encoder
+ ", duration: " + LogTime.getElapsedMillis(startTime));
}
} finally {
loadData.fetcher.cleanup();
}
sourceCacheGenerator =
new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
}
最终由cacheData方法完成磁盘缓存。
最后总结一张缓存逻辑图:
Glide缓存逻辑这里简单总结下:
取逻辑:
内存 > 磁盘 > 网络请求
-
内存:
上次刚被加载的资源(activeResources) > (最近被加载的资源)lruCache。 -
磁盘:
如果有主动设置DiskCacheStrategy,则按设置来。默认是取转换之后的资源(ResourceCache) > (DataCache)原始资源 -
网络请求:
走网络请求获取图片资源流。
存逻辑
-
内存:
当前被加载的图片资源存到activeResources中,下次加载资源切换,当前activeResources会remove然后被转移到lruCache。 -
磁盘:
网络请求成功之后,获取到资源流,然后看DiskCacheStrategy是否支持磁盘缓存,如果支持,会通过回调在SourceGenerator中通过cacheData进行资源缓存。
网友评论