本篇基于Glide4.6.1
前面的两篇文章走了一遍glide的请求网络图片并加载的流程,分析一个图片加载框架,其缓存机制是必须要知道的,一个完善的图片加载框架一般都包含内存缓存和硬盘缓存两种缓存策略。他们的作用各不相同,内存缓存主要是防止应用重复的把数据读到内存中,而硬盘缓存主要是防止应用重复的去网络上下载图片。这样才能让框架加载图片的速度更快,也能更省流量。下面就来看一下glide的缓存机制。
内存缓存
在内存缓存方面glide使用了两种方式,一个是我们常用的LruCache算法另一个是WeakReference弱引用的方式。下面看一下代码。代码在引擎类Engine的load方法中
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) {
Util.assertMainThread();
long startTime = LogTime.getLogTime();
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
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,
model,
key,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
onlyRetrieveFromCache,
options,
engineJob);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
engineJob.start(decodeJob);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}
(1) 首先根据很多的参数创建出一个key。这个key就是缓存存取的时候使用的key,之所以使用这么多参数创建,也是为了这个key的准确性,比如同一张图片我们给它的长宽不一样,所生成的key就不一样认为是两张图片。
(2) 然后 EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
先从弱引用中取图片 active可以翻译成活动,其实弱引用中缓存的图片是我们现在正在使用的图片,之所以多出来这个弱引用缓存主要为了当前使用的图片过多的时候可以防止这些图片被LruCache算法回收掉。进入loadFromActiveResources方法
@Nullable
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> active = activeResources.get(key);
if (active != null) {
active.acquire();
}
return active;
}
这里调用了一个get方法获取EngineResource
@Nullable
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;
}
这里调用了activeEngineResources的get方法,看看activeEngineResources
final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
可以看到activeEngineResources就是一个HashMap,内部存的就是我们的弱引用对象。弱引用中保存了我们的资源对象EngineResource。
(3)如果在弱引用中取出的资源不为null,就直接回调放回结果,否则就去LruCache缓存中取EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
进入到loadFromCache方法
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;
}
这里面调用getEngineResourceFromCache传入key值来获取资源
private EngineResource<?> getEngineResourceFromCache(Key key) {
Resource<?> cached = cache.remove(key);
final EngineResource<?> result;
if (cached == null) {
result = null;
} else if (cached instanceof EngineResource) {
result = (EngineResource<?>) cached;
} else {
result = new EngineResource<>(cached, true /*isMemoryCacheable*/, true /*isRecyclable*/);
}
return result;
}
这里的cache可以去Engine类创建的地方看到传入的是LruResourceCache对象,而LruResourceCache继承了LruCache,所以我们知道这是在LruCache中取资源。取完之后删除这资源并返回,回到loadFromCache方法中,activeResources.activate(key, cached);
把取到的资源在存在弱引用的缓存中。
OK这就是从内存中读取图片的逻辑,如果从两种内存缓存的方式中都没有得到资源,那就只好开启线程去硬盘上读取或者去网络上读取了。硬盘缓存等会再说,下面看看内存缓存是什么时候存进去的。
按照我们的正常逻辑,缓存应该是在图片加载完之后,把图片缓存,上一篇我们知道,图片加载完后会进入到EngineJob的onResourceReady方法中通过发送handler去主线程中执行
void handleResultOnMainThread() {
stateVerifier.throwIfRecycled();
if (isCancelled) {
resource.recycle();
release(false /*isRemovedFromQueue*/);
return;
} else if (cbs.isEmpty()) {
throw new IllegalStateException("Received a resource without any callbacks to notify");
} else if (hasResource) {
throw new IllegalStateException("Already have resource");
}
engineResource = engineResourceFactory.build(resource, isCacheable);
hasResource = true;
// Hold on to resource for duration of request so we don't recycle it in the middle of
// notifying if it synchronously released by one of the callbacks.
engineResource.acquire();
listener.onEngineJobComplete(this, key, engineResource);
//noinspection ForLoopReplaceableByForEach to improve perf
for (int i = 0, size = cbs.size(); i < size; i++) {
ResourceCallback cb = cbs.get(i);
if (!isInIgnoredCallbacks(cb)) {
engineResource.acquire();
cb.onResourceReady(engineResource, dataSource);
}
}
// Our request is complete, so we can release the resource.
engineResource.release();
release(false /*isRemovedFromQueue*/);
}
里面有一个方法listener.onEngineJobComplete(this, key, engineResource);
传入了key和resource进入这个方法中可以看到
public void onEngineJobComplete(EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
Util.assertMainThread();
// A null resource indicates that the load failed, usually due to an exception.
if (resource != null) {
resource.setResourceListener(key, this);
if (resource.isCacheable()) {
activeResources.activate(key, resource);
}
}
jobs.removeIfCurrent(key, engineJob);
}
这里就可以看到把这个resource保存在了弱引用缓存activeResources中。ok弱引用缓存完毕,那什么时候保存到Lru缓存中呢,看handleResultOnMainThread中最后engineResource.release();
方法
void release() {
if (acquired <= 0) {
throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
}
if (!Looper.getMainLooper().equals(Looper.myLooper())) {
throw new IllegalThreadStateException("Must call release on the main thread");
}
if (--acquired == 0) {
listener.onResourceReleased(key, this);
}
}
里面调用了onResourceReleased方法
public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
Util.assertMainThread();
activeResources.deactivate(cacheKey);
if (resource.isCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
可以看到在这里我们把资源保存在了Lru缓存中了。
上面的handleResultOnMainThread() 方法中我们看到在弱引用缓存之前调用了 engineResource.acquire();方法最后调用了engineResource.release() ; engineResource是我们的图片资源的包装对象。acquire();和release() ;方法都是对齐成员变量acquired的操控acquire();是给acquired加一和release() ;是给acquired减一。当acquired大于0的时候说明这个图片我们正在使用,当他等于0的时候,说明不现在没使用可以存在LruCache中。
硬盘缓存
跟上面一样,先看一在哪里取的。操作硬盘也是个耗时的操作,所以会在工作线程中执行,上一篇中我们知道DecodeJob就是我们的Runnable对象既工作线程,我们直接去它的run方法中
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);
}
}
runReason在类初始化的时候赋值INITIALIZE,所以从INITIALIZE开始stage = getNextStage(Stage.INITIALIZE);
判断我们从哪里去取如果是去磁盘取返回ResourceCacheGenerator
INITIALIZE:第一次加载的时候
SWITCH_TO_SOURCE_SERVICE:从硬盘缓存中取
DECODE_DATA:解析从网络加载完的数据
我们进入ResourceCacheGenerator的startNext()方法
public boolean startNext() {
List<Key> sourceIds = helper.getCacheKeys();
if (sourceIds.isEmpty()) {
return false;
}
List<Class<?>> resourceClasses = helper.getRegisteredResourceClasses();
if (resourceClasses.isEmpty()) {
if (File.class.equals(helper.getTranscodeClass())) {
return false;
}
throw new IllegalStateException(
"Failed to find any load path from " + helper.getModelClass() + " to "
+ helper.getTranscodeClass());
}
while (modelLoaders == null || !hasNextModelLoader()) {
resourceClassIndex++;
if (resourceClassIndex >= resourceClasses.size()) {
sourceIdIndex++;
if (sourceIdIndex >= sourceIds.size()) {
return false;
}
resourceClassIndex = 0;
}
Key sourceId = sourceIds.get(sourceIdIndex);
Class<?> resourceClass = resourceClasses.get(resourceClassIndex);
Transformation<?> transformation = helper.getTransformation(resourceClass);
// PMD.AvoidInstantiatingObjectsInLoops Each iteration is comparatively expensive anyway,
// we only run until the first one succeeds, the loop runs for only a limited
// number of iterations on the order of 10-20 in the worst case.
currentKey =
new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops
helper.getArrayPool(),
sourceId,
helper.getSignature(),
helper.getWidth(),
helper.getHeight(),
transformation,
resourceClass,
helper.getOptions());
cacheFile = helper.getDiskCache().get(currentKey);
if (cacheFile != null) {
sourceKey = sourceId;
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
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;
}
可以看到cacheFile = helper.getDiskCache().get(currentKey);
@Override
public File get(Key key) {
String safeKey = safeKeyGenerator.getSafeKey(key);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Get: Obtained: " + safeKey + " for for Key: " + key);
}
File result = null;
try {
final DiskLruCache.Value value = getDiskCache().get(safeKey);
if (value != null) {
result = value.getFile(0);
}
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Unable to get from disk cache", e);
}
}
return result;
}
可以看到这里就是从DiskLruCache硬盘缓存中取了DiskLruCache.Value value = getDiskCache().get(safeKey);
OK取的方法已经找到,下面开始看一下什么时候存的。
首先我们想一下存的时候肯定是图片从网络中获取之后才能存,所以去网络获取成功之后的地方上一篇我们知道请求完之后会进入
public void onDataReady(Object data) {
DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
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);
}
}
这里判断了我们的缓存策略,缓存策略我们从开始初始化的时候可以配置
RequestOptions options = new RequestOptions()
.placeholder(R.mipmap.ic_launcher)//加载成功之前占位图
.error(R.mipmap.ic_launcher)//加载错误之后的错误图
.override(400,400)//指定图片的尺寸
.fitCenter()//指定图片的缩放类型为fitCenter (等比例缩放图片,宽或者是高等于ImageView的宽或者是高。)
.centerCrop()//指定图片的缩放类型为centerCrop (等比例缩放图片,直到图片的狂高都大于等于ImageView的宽度,然后截取中间的显示。)
.circleCrop()//指定图片的缩放类型为centerCrop (圆形)
.skipMemoryCache(true)//跳过内存缓存
.diskCacheStrategy(DiskCacheStrategy.ALL)//缓存所有版本的图像
.diskCacheStrategy(DiskCacheStrategy.NONE)//跳过磁盘缓存
.diskCacheStrategy(DiskCacheStrategy.DATA)//只缓存原来分辨率的图片
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)//只缓存最终的图片
最后几句我们配置了我们的硬盘缓存策略,以后硬盘缓存就按照这些策略来。
在回到onDataReady中,如果我们允许缓存就给dataToCache 字段赋值,然后下一句cb.reschedule()
public void reschedule() {
runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
callback.reschedule(this);
}
进入 callback.reschedule(this);中
public void reschedule(DecodeJob<?> job) {
getActiveSourceExecutor().execute(job);
}
可以看到更改runReason为SWITCH_TO_SOURCE_SERVICE然后又执行线程里的工作,所以又会回到run()方法中执行 runGenerators()
public boolean startNext() {
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
cacheData(data);
}
if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
return true;
}
sourceCacheGenerator = null;
loadData = null;
boolean started = false;
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;
}
因为dataToCache 不为null,第一判断就可以进入cacheData(data);方法中了
private void cacheData(Object dataToCache) {
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);
} finally {
loadData.fetcher.cleanup();
}
sourceCacheGenerator =
new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
}
这里就可以看到保存到硬盘中啦helper.getDiskCache().put(originalKey, writer);
Ok 啦glide的缓存流程就分析完啦。