前言
由于最近项目加载图片这一块用到了Glide框架,以前也用到过,其最核心的部分应该是属于缓存的机制,
最近花了点时间分析其缓存原理
最简单的Glide的用法:
Glide.with(context) .asBitmap() .load(imageUrl) .into(imageView);
最终都是要调用into方法:
RequestBuilder :
return into(
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
requestOptions);
}
该方法最终会返回一个ThumbnailRequestCoordinator对象,coordinator.setRequests(fullRequest, thumbRequest);从这个可以看出最终是会根据Option返回两个request
这Request是一个SingleRequest对象
SingleRequest.obtain(....);
这边直接跳到SingleRequest的begin方法:
@Override
public void begin() {
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.
//代码1
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));
}
}
其中重要的是代码1
//代码1
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
target.getSize(this);
}
这个地方需要注意的就是这个地方 的overrideWidth和overrideHeight,因为这两个宽高会用于生成缓存的key的参数,从这里可以看出,Glide的缓存是根据给定的宽高或者view的宽高缓存当前的图片,这个是Picaso等图片加载框架不同的地方。
target.getSize(this);
如果没有设定特定的宽高,会去拿当前view的宽高
void getSize(@NonNull SizeReadyCallback cb) {
int currentWidth = getTargetWidth();
int currentHeight = getTargetHeight();
if (isViewStateAndSizeValid(currentWidth, currentHeight)) {
cb.onSizeReady(currentWidth, currentHeight);
return;
}
// We want to notify callbacks in the order they were added and we only expect one or two
// callbacks to be added a time, so a List is a reasonable choice.
if (!cbs.contains(cb)) {
cbs.add(cb);
}
if (layoutListener == null) {
ViewTreeObserver observer = view.getViewTreeObserver();
layoutListener = new SizeDeterminerLayoutListener(this);
observer.addOnPreDrawListener(layoutListener);
}
上面的方法最终会调用到onSizeReady(SingleRequest):
public void onSizeReady(int width, int height) {
stateVerifier.throwIfRecycled();
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);
// 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));
}
}
其中调用Engine的load方法,才是真正去拿图片的过程。
备注:严格来讲,Glide的缓存方式是三级缓存的
engine.load :
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 (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}
//二级缓存,用LruCahce做内存缓存处理
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
//下面的去decode数据的时候,还会更加相应的key去硬盘缓存拿数据(DiskLruCache)
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb);
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,
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 (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}
先分析一下用去作为换成的的生成:
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
通过相应的参数生成一个EngineKey ,里面对hashCode和equals分别做了重写,也就是说,如果一张
图片的宽高,图片路劲等参数不变,但是做了旋转或者其他不改变参数的操作时,
拿到的会将是缓存的图片,这个时候往往是定义新的singature,让在路径不变的情况下,也可以改变加载新的图片
一级缓存:
@Nullable
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
if (!isMemoryCacheable) {
return null;
}
EngineResource<?> active = activeResources.get(key);
if (active != null) {
active.acquire();
}
return active;
}
其中activeResources是一个ActiveResources对象,里面维护了一Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();这样的HashMap,用ResourceWeakReference弱应用保存
当前的资源,如果拿到直接调用cb.onResourceReady(active, DataSource.MEMORY_CACHE)
如何在弱引用缓存(一级缓存)拿不到数据的时候,拿将会从二级缓存(LruCache)拿数据,并且将数据再
放到弱引用缓存(一级缓存):
二级缓存(LruCache)
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
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;
}
private EngineResource<?> getEngineResourceFromCache(Key key) {
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;
}
private final MemoryCache cache 这个cache对象其实就是一个LruResourceCache对象。
从内存换成拿到资源后,放入一级缓存
if (cached != null) {
cached.acquire();
activeResources.activate(key, cached);
}
void activate(Key key, EngineResource<?> resource) {
ResourceWeakReference toPut =
new ResourceWeakReference(
key,
resource,
getReferenceQueue(),
isActiveResourceRetentionAllowed);
ResourceWeakReference removed = activeEngineResources.put(key, toPut);
if (removed != null) {
removed.reset();
}
}
如果从上面一级缓存(弱引用)和二级缓存(LruCache)拿不到数据,会去硬盘缓存(DiskLruCache)拿数据
直接跳转到DecodeJob方法里面的run方法:
三级缓存
@Override
public void run() {
DataFetcher<?> localFetcher = currentFetcher;
try {
if (isCancelled) {
notifyFailed();
return;
}
runWrapped();
} catch (Throwable t) {
} finally {
}
}
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);
}
}
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);
}
}
private void runGenerators() {
currentThread = Thread.currentThread();
startFetchTime = LogTime.getLogTime();
boolean isStarted = false;
while (!isCancelled && currentGenerator != null
&& !(isStarted = currentGenerator.startNext())) {
stage = getNextStage(stage);
currentGenerator = getNextGenerator();
if (stage == Stage.SOURCE) {
reschedule();
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,另外两个方法其实就是最终拿到要使用的
DataFetcherGenerator。
其中currentGenerator.startNext():
分析DataCacheGenerator里面的startNext:
@Override
public boolean startNext() {
while (modelLoaders == null || !hasNextModelLoader()) {
sourceIdIndex++;
if (sourceIdIndex >= cacheKeys.size()) {
return false;
}
Key sourceId = cacheKeys.get(sourceIdIndex);
// PMD.AvoidInstantiatingObjectsInLoops The loop iterates a limited number of times
// and the actions it performs are much more expensive than a single allocation.
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
Key originalKey = new DataCacheKey(sourceId, helper.getSignature());//(三级缓存)
cacheFile = helper.getDiskCache().get(originalKey);
if (cacheFile != null) {
this.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(originalKey); 这个代码就是去硬盘缓存(三级缓存)看能否拿到对应的数据,
这里通过生成一个新的key去cache里面拿数据.
DiskCache getDiskCache() {
return diskCacheProvider.getDiskCache();
}
private static class LazyDiskCacheProvider implements DecodeJob.DiskCacheProvider {
private final DiskCache.Factory factory;
private volatile DiskCache diskCache;
LazyDiskCacheProvider(DiskCache.Factory factory) {
this.factory = factory;
}
@VisibleForTesting
synchronized void clearDiskCacheIfCreated() {
if (diskCache == null) {
return;
}
diskCache.clear();
}
@Override
public DiskCache getDiskCache() {
if (diskCache == null) {
synchronized (this) {
if (diskCache == null) {
diskCache = factory.build();
}
if (diskCache == null) {
diskCache = new DiskCacheAdapter();
}
}
}
return diskCache;
}
}
其中factory正是初始化Glide对象生成的
if (diskCacheFactory == null) {
diskCacheFactory = new InternalCacheDiskCacheFactory(context);
}
@Override
public DiskCache build() {
File cacheDir = cacheDirectoryGetter.getCacheDirectory();
if (cacheDir == null) {
return null;
}
if (!cacheDir.mkdirs() && (!cacheDir.exists() || !cacheDir.isDirectory())) {
return null;
}
return DiskLruCacheWrapper.create(cacheDir, diskCacheSize);
}
}
从上面可以知道 cacheFile = helper.getDiskCache().get(originalKey);其实是从
DiskLruCacheWrapper拿到的缓存,而DiskLruCacheWrapper正是对DiskLruCache的一个包装类
分析到上面,知道这个时候已经是从三级缓存拿数据,如果没拿到就去decode相应的数据?
但是这还有一个非常值得注意的地方就是去拿三级缓存数据用到的key,先看一下key的源码:
public interface Key {
String STRING_CHARSET_NAME = "UTF-8";
Charset CHARSET = Charset.forName(STRING_CHARSET_NAME);
/**
* Adds all uniquely identifying information to the given digest.
*
* <p> Note - Using {@link java.security.MessageDigest#reset()} inside of this method will result
* in undefined behavior. </p>
*/
void updateDiskCacheKey(@NonNull MessageDigest messageDigest);
/**
* For caching to work correctly, implementations <em>must</em> implement this method and
* {@link #hashCode()}.
*/
@Override
boolean equals(Object o);
/**
* For caching to work correctly, implementations <em>must</em> implement this method and
* {@link #equals(Object)}.
*/
@Override
int hashCode();
}
从上面发现了一个方法:
void updateDiskCacheKey(@NonNull MessageDigest messageDigest);
用于在一级缓存和二级缓存拿数据的key, 都是通过key去HashMap数据源拿数据,而HashMa判断一个key是否是同一个,只是用到hashCode和equal是否相同,所以并没有用到上面那个方法,而三级缓存就在获取一个key的时候,比较特殊,会用到上面那个方法去拿到相应的key,所以如果一个图片在一级缓存,二级缓存都没有,但是在三级缓存有,是通过 updateDiskCacheKey去生成key的.
看一下如何用updateDiskCacheKey这个方法的
@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 {
// It is possible that the there will be a put in between these two gets. If so that shouldn't
// be a problem because we will always put the same value at the same key so our input streams
// will still represent the same data.
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;
}
String safeKey = safeKeyGenerator.getSafeKey(key);生成对应的String类型的key
public String getSafeKey(Key key) {
String safeKey;
synchronized (loadIdToSafeHash) {
safeKey = loadIdToSafeHash.get(key);
}
if (safeKey == null) {
safeKey = calculateHexStringDigest(key);
}
synchronized (loadIdToSafeHash) {
loadIdToSafeHash.put(key, safeKey);
}
return safeKey;
}
private String calculateHexStringDigest(Key key) {
PoolableDigestContainer container = Preconditions.checkNotNull(digestPool.acquire());
try {
key.updateDiskCacheKey(container.messageDigest);//生成相应的key
// calling digest() will automatically reset()
return Util.sha256BytesToHex(container.messageDigest.digest());
} finally {
digestPool.release(container);
}
}
从上面可以知道,是通过key.updateDiskCacheKey(container.messageDigest)去更新messageDigest
然后在将messageDigest里面的数据digest()返回
如果你想修改一个宽高,路径一样的图片,但是又在一级和二级缓存里面没有的图片,如果没有实现
updateDiskCacheKey该方法根据相应的数据更新key,也可能会拿到三级缓存的图片而返回不是想要的
图片
上面就是分析完从三级缓存拿数据
以上如有说的不对的地方,请指正
TXH
网友评论