Glide 源码阅读笔记(四)
在第一篇文章中我简单介绍了 Glide
实例的创建过程,重点介绍了 Glide
的内存缓存实现和磁盘的缓存实现:Glide 源码阅读笔记(一)
在第二篇文章中介绍了 Glide
对生命周期的管理: Glide 源码阅读笔记(二)
在第三篇文章中介绍了 Request
和 RequestCoordinator
,还简单介绍了 Registry
中的核心组件:Glide 源码阅读笔记(三)
今天的内容主要是介绍 SingleRequest
是如何处理一个图片加载的任务。
源码阅读基于 Glide 4.16.0
版本
SingleRequest
我们在前面的文章中有说到真正实现图片任务加载的是 SingleRequest
,我们就以 SingleRequest#begin()
方法作为开端来分析:
Java
@Override
public void begin() {
synchronized (requestLock) {
assertNotCallingCallbacks();
stateVerifier.throwIfRecycled();
startTime = LogTime.getLogTime();
// 如何 model 为空,直接回调失败,同时返回方法
if (model == null) {
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
width = overrideWidth;
height = overrideHeight;
}
int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
onLoadFailed(new GlideException("Received null model"), logLevel);
return;
}
// 如果当前的状态是 RUNNING,直接抛出异常
if (status == Status.RUNNING) {
throw new IllegalArgumentException("Cannot restart a running request");
}
// 如果当前的状态是 COMPLETE,直接回调成功,同时返回方法
if (status == Status.COMPLETE) {
onResourceReady(
resource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
return;
}
experimentalNotifyRequestStarted(model);
cookie = GlideTrace.beginSectionAsync(TAG);
// 更新状态
status = Status.WAITING_FOR_SIZE;
// 如果当前的尺寸合法
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
// 通知尺寸已经准备好
onSizeReady(overrideWidth, overrideHeight);
} else {
// 等待 target (ImageView)完成尺寸的测量。
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));
}
}
}
private enum Status {
/** Created but not yet running. */
PENDING,
/** In the process of fetching media. */
RUNNING,
/** Waiting for a callback given to the Target to be called to determine target dimensions. */
WAITING_FOR_SIZE,
/** Finished loading media successfully. */
COMPLETE,
/** Failed to load media, may be restarted. */
FAILED,
/** Cleared by the user with a placeholder set, may be restarted. */
CLEARED,
}
这里简单说明一下上面的代码:
- 如何
model
(加载的url
就属于model
) 为空,直接回调失败,同时返回方法。 - 如果当前的状态是
RUNNING
,直接抛出异常。 - 如果当前的状态是
COMPLETE
,直接回调成功,同时返回方法。 - 检查当前的
with
和height
是否合法,如果合法就直接调用onSizeReady()
方法,如果不合法需要等待target
完成测量,也就是等待ImageView
完成测量。 - 最后回调加载开始。
我们来看看 ViewTarget#getSize()
是如何获取 View
尺寸的。
Java
@CallSuper
@Override
public void getSize(@NonNull SizeReadyCallback cb) {
sizeDeterminer.getSize(cb);
}
继续调用 SizeDeterminer#getSize()
方法。
Java
void getSize(@NonNull SizeReadyCallback cb) {
// 获取当前 View 的尺寸
int currentWidth = getTargetWidth();
int currentHeight = getTargetHeight();
// 如果当前 View 的尺寸合法,直接回调成功
if (isViewStateAndSizeValid(currentWidth, currentHeight)) {
cb.onSizeReady(currentWidth, currentHeight);
return;
}
// 添加到监听中
if (!cbs.contains(cb)) {
cbs.add(cb);
}
if (layoutListener == null) {
// 在 ViewTreeObserver 中添加 PreDraw 时的监听。
ViewTreeObserver observer = view.getViewTreeObserver();
layoutListener = new SizeDeterminerLayoutListener(this);
observer.addOnPreDrawListener(layoutListener);
}
}
private int getTargetHeight() {
int verticalPadding = view.getPaddingTop() + view.getPaddingBottom();
LayoutParams layoutParams = view.getLayoutParams();
int layoutParamSize = layoutParams != null ? layoutParams.height : PENDING_SIZE;
return getTargetDimen(view.getHeight(), layoutParamSize, verticalPadding);
}
private int getTargetWidth() {
int horizontalPadding = view.getPaddingLeft() + view.getPaddingRight();
LayoutParams layoutParams = view.getLayoutParams();
int layoutParamSize = layoutParams != null ? layoutParams.width : PENDING_SIZE;
return getTargetDimen(view.getWidth(), layoutParamSize, horizontalPadding);
}
private int getTargetDimen(int viewSize, int paramSize, int paddingSize) {
int adjustedParamSize = paramSize - paddingSize;
if (adjustedParamSize > 0) {
return adjustedParamSize;
}
if (waitForLayout && view.isLayoutRequested()) {
return PENDING_SIZE;
}
int adjustedViewSize = viewSize - paddingSize;
if (adjustedViewSize > 0) {
return adjustedViewSize;
}
if (!view.isLayoutRequested() && paramSize == LayoutParams.WRAP_CONTENT) {
if (Log.isLoggable(TAG, Log.INFO)) {
// ...
}
return getMaxDisplayLength(view.getContext());
}
return PENDING_SIZE;
}
获取 View
的尺寸在我们的实际的开发中也是经常遇到的,我们来看看 Glide
是怎么做的,它给我们写了一个非常好的样板代码。
-
获取当前的
View
的尺寸,并判断尺寸是否合法(其实就是大于 0 就合法),他获取当前尺寸的方式有两种,首先从View
的LayoutParams
中获取尺寸,如果这个尺寸合法,就直接使用,如果LayoutParams
中的尺寸不合法,就从View
中去获取,计算可用的尺寸时都会减去Padding
值。 -
如果当前
View
的尺寸不可用,在ViewTreeObserver
中添加一个OnPreDrawListener
去监听,它的实现类是SizeDeterminerLayoutListener
。
我们继续看看 SizeDeterminerLayoutListener
的代码:
Java
private static final class SizeDeterminerLayoutListener
implements ViewTreeObserver.OnPreDrawListener {
private final WeakReference<SizeDeterminer> sizeDeterminerRef;
SizeDeterminerLayoutListener(@NonNull SizeDeterminer sizeDeterminer) {
sizeDeterminerRef = new WeakReference<>(sizeDeterminer);
}
@Override
public boolean onPreDraw() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "OnGlobalLayoutListener called attachStateListener=" + this);
}
SizeDeterminer sizeDeterminer = sizeDeterminerRef.get();
if (sizeDeterminer != null) {
sizeDeterminer.checkCurrentDimens();
}
return true;
}
}
注意这里它持有的 SizeDeterminer
是弱引用,防止内存泄漏。当 onPreDraw()
方法回调时,会调用 SizeDeterminer#checkCurrentDimens()
方法,我们看看这个方法的实现:
Java
@Synthetic
void checkCurrentDimens() {
if (cbs.isEmpty()) {
return;
}
int currentWidth = getTargetWidth();
int currentHeight = getTargetHeight();
if (!isViewStateAndSizeValid(currentWidth, currentHeight)) {
return;
}
notifyCbs(currentWidth, currentHeight);
clearCallbacksAndListener();
}
先获取 View
的尺寸,同样先是获取 LayoutParams
中的,如果获取失败再去直接拿 View
中的,如果获取到的尺寸是可用的,通知每个 Callback
,然后移除所有的 Callback
。
我们再看看 SingleRequest
中的 onSizeReady()
方法是如何处理的:
Java
@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);
if (status != Status.RUNNING) {
loadStatus = null;
}
if (IS_VERBOSE_LOGGABLE) {
logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
}
}
}
上面的代码比较简单,就是直接调用 Engine#load()
方法,其中返回值 loadStatus
可以用来控制加载的任务。
Engine
我们继续看看 Engine#load()
方法中具体做了什么事情:
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,
Executor callbackExecutor) {
long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
EngineKey key =
keyFactory.buildKey(
model,
signature,
width,
height,
transformations,
resourceClass,
transcodeClass,
options);
EngineResource<?> memoryResource;
synchronized (this) {
// 先尝试从内存缓存中加载
memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
if (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);
}
}
// 内存缓存获取成功,直接通知回调
cb.onResourceReady(
memoryResource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
return null;
}
上面代码比较简单,通过 loadFromMemory()
去加载内存中的缓存,如果内存中的缓存为空,再通过 waitForExistingOrStartNewJob()
方法去加载磁盘中的缓存或者通过网络去加载。
加载内存缓存
我们先看看 loadFromMemory()
方法是如何加载内存中的缓存的:
Java
@Nullable
private EngineResource<?> loadFromMemory(
EngineKey key, boolean isMemoryCacheable, long startTime) {
if (!isMemoryCacheable) {
return null;
}
// 加载存活的资源
EngineResource<?> active = loadFromActiveResources(key);
if (active != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return active;
}
// 加载缓存中的资源
EngineResource<?> cached = loadFromCache(key);
if (cached != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return cached;
}
return null;
}
内存中的缓存分为两种,存活的资源和缓存中的资源。你可能有点懵,我来解释一下。
- 存活的资源
存活的资源其实就是别的地方正在使用的资源,例如ImageViewA
加载了一个资源X
,而且已经显示在屏幕上了,这个时候ImageViewB
也去加载资源X
,这个时候就不需要再去加载,直接复用ImageViewA
加载的资源X
就好了。 - 缓存中的资源
这个就是内存缓存的资源,我们在第一篇文章中就有介绍内存的缓存类,它的实现类是LruResourceCache
,缓存的大小是 2 倍屏幕大小图片所占用的内存,如果忘记了的同学可以看看我的第一篇文章。
我们来看看 loadFromActiveResources()
方法是如何加载存活的资源的:
Java
@Nullable
private EngineResource<?> loadFromActiveResources(Key key) {
EngineResource<?> active = activeResources.get(key);
if (active != null) {
active.acquire();
}
return active;
}
通过 ActivityResources#get()
方法来获取资源的,资源用 EngineResource
来封装,获取到的资源然后回调用 EngineResource#acquire()
方法,表示增加一次它的引用。
我们来看看 ActivityResources#get()
方法的实现:
Java
@VisibleForTesting final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
@Nullable
synchronized 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;
}
它是直接储存在 HashMap
中,Value
是用 ResourceWeakReference
封装,他继承于 WeakReference
,也就是我们常说的弱引用。
ActivityResources#activate()
方法是用来添加资源对象,而 ActivityResources#deactivate()
方法就是用来回收资源对象,我们来看看他们的实现:
Java
synchronized void activate(Key key, EngineResource<?> resource) {
ResourceWeakReference toPut =
new ResourceWeakReference(
key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);
ResourceWeakReference removed = activeEngineResources.put(key, toPut);
if (removed != null) {
removed.reset();
}
}
synchronized void deactivate(Key key) {
ResourceWeakReference removed = activeEngineResources.remove(key);
if (removed != null) {
removed.reset();
}
}
上面的代码比较简单我就不再说明了,不过这里还有一个关键点,就是在构建 ResourceWeakReference
弱引用时添加了一个引用队列 resourceReferenceQueue
,被移除了的弱引用会被添加到这个引用队列中,不清楚的同学可以去查找一下相关的资料。
在构造函数中会在一个线程池中监听 resourceReferenceQueue
的状态:
Java
@VisibleForTesting
ActiveResources(
boolean isActiveResourceRetentionAllowed, Executor monitorClearedResourcesExecutor) {
this.isActiveResourceRetentionAllowed = isActiveResourceRetentionAllowed;
this.monitorClearedResourcesExecutor = monitorClearedResourcesExecutor;
monitorClearedResourcesExecutor.execute(
new Runnable() {
@Override
public void run() {
cleanReferenceQueue();
}
});
}
我们再来看看 cleanReferenceQueue()
方法的实现:
Java
@Synthetic
void cleanReferenceQueue() {
while (!isShutdown) {
try {
ResourceWeakReference ref = (ResourceWeakReference) resourceReferenceQueue.remove();
cleanupActiveReference(ref);
// This section for testing only.
DequeuedResourceCallback current = cb;
if (current != null) {
current.onResourceDequeued();
}
// End for testing only.
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
其实就是监控被移除的 ResourceWeakReference
,然后再做一些清理工作。
到这里 ActivityResources
我们就介绍完了。前面我们说到 EngineResource
会调用 acquire()
方法来增加引用次数。我们来看看它:
Java
synchronized void acquire() {
if (isRecycled) {
throw new IllegalStateException("Cannot acquire a recycled resource");
}
++acquired;
}
直接把 acquired
变量加 1。我们再看看减少引用次数的方法 release()
的实现:
Java
void release() {
boolean release = false;
synchronized (this) {
if (acquired <= 0) {
throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
}
if (--acquired == 0) {
release = true;
}
}
if (release) {
listener.onResourceReleased(key, this);
}
}
直接把 acquired
变量减 1,如果 acquired
变成 0 了就表示没有引用了,那么就会调用 listener.onResourceReleased()
方法,这个 listener
其实就是 Engine
,我们继续看看 Engine#onResourceReleased()
方法怎么处理:
Java
@Override
public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
activeResources.deactivate(cacheKey);
if (resource.isMemoryCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource, /* forceNextFrame= */ false);
}
}
上面代码简单,如果允许使用内存缓存,直接缓存到 LruResourceCache
中,如果不允许缓存直接通过 ResourceRecycler#recycle()
方法回收。
到这里存活的资源的管理方式就介绍完了。
我们继续看看 loadFromCache()
方法获取缓存中的资源:
Java
private EngineResource<?> loadFromCache(Key key) {
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,
/* isMemoryCacheable= */ true,
/* isRecyclable= */ true,
key,
/* listener= */ this);
}
return result;
}
上面的代码非常简单,直接从 LruResourceCache
获取就行了,同时会移除对应的缓存,如果获取到的缓存不是 EngineResource
类型,还会重新用 EngineResource
封装一下,获取到的缓存还会被添加到 ActiveResources
中,表示当前存活的资源。
加载磁盘中的缓存
我们继续看看 Engine#load()
方法中调用的 waitForExistingOrStartNewJob()
是如何工作的:
Java
private <R> LoadStatus waitForExistingOrStartNewJob(
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,
Executor callbackExecutor,
EngineKey key,
long startTime) {
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb, callbackExecutor);
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, callbackExecutor);
engineJob.start(decodeJob);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}
所有的任务都会被添加到 jobs
成员变量中,这里会创建两个 Job
,分别是 DecodeJob
和 EngineJob
,他们都互相持有对方的引用,EngineJob
主要负责线程调度和通知上层 Engine
各种加载状态,而 DecodeJob
主要负责图片加载任务的处理。最后返回一个 LoadStatus
对象,SingleRequest
就可以通过这个对象来取消任务。
我们看看 EngineJob#start()
方法的代码:
Java
public synchronized void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
GlideExecutor executor =
decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor();
executor.execute(decodeJob);
}
如果允许磁盘缓存,DecodeJob
就会被添加到 diskCacheExecutor
中执行。
我们继续看看 DecodeJob#run()
方法:
Java
@Override
public void run() {
GlideTrace.beginSectionFormat("DecodeJob#run(reason=%s, model=%s)", runReason, model);
DataFetcher<?> localFetcher = currentFetcher;
try {
if (isCancelled) {
notifyFailed();
return;
}
runWrapped();
} catch (CallbackException e) {
throw e;
} catch (Throwable t) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(
TAG,
"DecodeJob threw unexpectedly" + ", isCancelled: " + isCancelled + ", stage: " + stage,
t);
}
if (stage != Stage.ENCODE) {
throwables.add(t);
notifyFailed();
}
if (!isCancelled) {
throw t;
}
throw t;
} finally {
if (localFetcher != null) {
localFetcher.cleanup();
}
GlideTrace.endSection();
}
}
忽略各种错误处理和通知,这里会调用 runWrapped()
方法,我们继续跟进:
Java
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);
}
}
/** Why we're being executed again. */
private enum RunReason {
/** The first time we've been submitted. */
INITIALIZE,
/** We want to switch from the disk cache service to the source executor. */
SWITCH_TO_SOURCE_SERVICE,
/**
* We retrieved some data on a thread we don't own and want to switch back to our thread to
* process the data.
*/
DECODE_DATA,
}
默认的 RunReason
是 INITIALIZE
,所以首先会调用 getNextState()
,然后调用 getNextGenerator()
,最后调用 runGenerators()
,分别看看上面的方法:
Java
private Stage getNextStage(Stage current) {
switch (current) {
case INITIALIZE:
// 判断当前是否支持 ResourceCache,如果支持下一个状态就是 RESOURCE_CACHE,如果不支持就重新计算
return diskCacheStrategy.decodeCachedResource()
? Stage.RESOURCE_CACHE
: getNextStage(Stage.RESOURCE_CACHE);
case RESOURCE_CACHE:
// 判断是否支持 DataCache,如果支持下一个状态就是 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.
// 判断是不是只能使用磁盘缓存,如果是下个状态就是 FINISHED,反之就是 SOURCE。
return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: " + current);
}
}
我相信很多人对 RESOUCE_CACHE
与 DATA_CACHE
可能分不清,我这里简单解释一下上面几个比较重要的状态。
RESOUCE_CACHE
缓存处理过的原来的网络图片,比如图片的尺寸等等优化过,也可能根据ScaleType
裁剪过。DATA_CACHE
缓存未经过原来处理过的网络图片,也就是图片原来是什么样子,缓存就是什么样子。SOURCE
通过网络去获取图片资源。
Glide
默认的磁盘缓存策略是 AUTOMATIC
,默认的网络图片是使用的 DATA_CACHE
。
我们继续看 getNextGenerator()
方法的实现:
Java
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);
}
}
上面代码非常简单,RESOURCE_CACHE
由 ResourceCacheGenerator
来处理;DATA_CACHE
由 DataCacheGenerator
来处理;SOURCE
由 SourceGenerator
来处理。
我们继续看看 runGenerators()
方法:
Java
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(RunReason.SWITCH_TO_SOURCE_SERVICE);
return;
}
}
// We've run out of stages and generators, give up.
if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
notifyFailed();
}
}
上面方法也是比较简单直接调用 Generator#startNext()
方法来执行对应的任务,如果方法返回 false
表示继续执行下一个状态的 Generator
,最后直到执行完所有的 Generator
,注意这里如果状态是 SOURCE
,也就是网络请求的状态,会调用 reschedule()
方法,同时 RunReason
是 SWITCH_TO_SOURCE_SERVICE
,其实就是网络请求的 Generator
会在另外的线程执行。
看看 reschedule()
方法:
Java
private void reschedule(RunReason runReason) {
this.runReason = runReason;
callback.reschedule(this);
}
这个 callback
其实就是 EngineJob
,我们再看看 EngineJob#reschedule()
:
Java
@Override
public void reschedule(DecodeJob<?> job) {
getActiveSourceExecutor().execute(job);
}
我们看到这里会切换至 SourceExecutor
继续执行后续的任务。
我们来看看 ResourceCacheGenerator#startNext()
是如何执行加载 RESOURCE_CACHE
缓存任务的:
Java
@Override
public boolean startNext() {
GlideTrace.beginSection("ResourceCacheGenerator.startNext");
try {
// 获取所有的支持的 ModelLoader 中的 LoadData 对象的 Keys。
List<Key> sourceIds = helper.getCacheKeys();
if (sourceIds.isEmpty()) {
return false;
}
// 获取所有的可用的 Transcoder 转换后的数据的 Class 对象(我们的 demo 中是 Drawable)
List<Class<?>> resourceClasses = helper.getRegisteredResourceClasses();
if (resourceClasses.isEmpty()) {
if (File.class.equals(helper.getTranscodeClass())) {
return false;
}
// 如果没有可用的 Transcoder 直接抛出异常。
throw new IllegalStateException(
"Failed to find any load path from "
+ helper.getModelClass()
+ " to "
+ helper.getTranscodeClass());
}
// 遍历所有的 ModelLoader 和 ResourceClasses 对象生成的 key,通过这个 key 去查找到一个可用的缓存文件对应的 ModelLoader。
while (modelLoaders == null || !hasNextModelLoader()) {
resourceClassIndex++;
if (resourceClassIndex >= resourceClasses.size()) {
sourceIdIndex++;
if (sourceIdIndex >= sourceIds.size()) {
return false;
}
resourceClassIndex = 0;
}
// 获取对应的 ModelLoader 中的 Key
Key sourceId = sourceIds.get(sourceIdIndex);
// 获取对应 Transcoder 输出的 class
Class<?> resourceClass = resourceClasses.get(resourceClassIndex);
// 获取裁剪方式的 Transformation
Transformation<?> transformation = helper.getTransformation(resourceClass);
// 通过上面的参数生成各种 Key。
currentKey =
new ResourceCacheKey( // NOPMD AvoidInstantiatingObjectsInLoops
helper.getArrayPool(),
sourceId,
helper.getSignature(),
helper.getWidth(),
helper.getHeight(),
transformation,
resourceClass,
helper.getOptions());
// 通过生成的 Key 从 DiskLruCache 中去查找缓存文件.
cacheFile = helper.getDiskCache().get(currentKey);
if (cacheFile != null) {
// 缓存文件不为空,去查找能够处理 File 类型的 ModelLoaders。
sourceKey = sourceId;
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}
loadData = null;
boolean started = false;
// 遍历找到的所有的 ModelLoader,找到一个可用的去加载 File 缓存文件。
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;
通过 ModelLoader 中 loadData 中的 fetcher 去加载缓存的 File
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
// 最后的返回结果表示是否拿到可用的缓存.
return started;
} finally {
GlideTrace.endSection();
}
}
如果对 Registry
中的核心组件不熟悉的同学可以看看我前面的一篇文章,总的来说处理 model
(url
) 的流程就是:ModelLoader
-> Decoder
-> Transcoder
。
上面的代码稍微有点小小的复杂,总的来说就是遍历所有的可用的 ModelLoader
和 Trascoder
最后的输出 Resource
的 Class
对象生成的 Key
,然后通过这个 Key
从本地缓存中去查找对应的文件,如果有查找到对应的文件,然后去查找处理 File
类型的 ModelLoader
,然后通过 ModelLoader
去加载对应的 File
。
这里得注意一下查找缓存文件所对应的 ResourceCacheKey
,它是由 ModelLoader
( Url
)、with
、height
、ResourceClass
(Trascoder
转换后的对象 Class
)和 Signature
。我这里要单独说明一下这个 Signature
,除了别的参数外,我们可以简单的理解为通过 model
(url
) 来生成缓存文件 Key
的,大多数情况下我们开发中也是一个 url
对应的一个图片,不过这里有例外,一个 url
可能对应多个图片,比如我有这样一个 url
表示今日美图: https://img.tans.com
。也就是这个 url
会根据日期去返回不同的图片。如果是这样 Glide
默认的缓存机制就会出现问题,当我今天加载图片并缓存后,第二天还是会使用这个缓存,而不会去真正去请求网络,这不是我们希望看到的结果。所以我们需要自定义一个 Signature
来生成组合的 Key
,我们就可以使用日期来作为 Signature
,这样我们就能够解决上面遇到的问题。
上面多说了一点题外的话,我们接着看加载 File
的 ModelLoader
加载成功后接着怎么处理,加载成功的回调方法是 ResourceCacheGenerator#onDataReady()
:
Java
@Override
public void onDataReady(Object data) {
cb.onDataFetcherReady(
sourceKey, data, loadData.fetcher, DataSource.RESOURCE_DISK_CACHE, currentKey);
}
继续回调 DecodeJob#onDataFetcherReady()
方法:
Java
@Override
public void onDataFetcherReady(
Key sourceKey, Object data, DataFetcher<?> fetcher, DataSource dataSource, Key attemptedKey) {
this.currentSourceKey = sourceKey;
this.currentData = data;
this.currentFetcher = fetcher;
this.currentDataSource = dataSource;
this.currentAttemptingKey = attemptedKey;
this.isLoadingFromAlternateCacheKey = sourceKey != decodeHelper.getCacheKeys().get(0);
// 只有执行了网络加载才会切线程,缓存不用切线程
if (Thread.currentThread() != currentThread) {
reschedule(RunReason.DECODE_DATA);
} else {
GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData");
try {
// 缓存的代码执行这里
decodeFromRetrievedData();
} finally {
GlideTrace.endSection();
}
}
}
后续的代码继续调用 decodeFromRetrievedData()
方法:
Java
private void decodeFromRetrievedData() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey(
"Retrieved data",
startFetchTime,
"data: "
+ currentData
+ ", cache key: "
+ currentSourceKey
+ ", fetcher: "
+ currentFetcher);
}
Resource<R> resource = null;
try {
// 解码数据
resource = decodeFromData(currentFetcher, currentData, currentDataSource);
} catch (GlideException e) {
e.setLoggingDetails(currentAttemptingKey, currentDataSource);
throwables.add(e);
}
if (resource != null) {
// 通知解码成功
notifyEncodeAndRelease(resource, currentDataSource, isLoadingFromAlternateCacheKey);
} else {
runGenerators();
}
}
首先通过 decodeFromData()
方法来解码数据,如果解码成功,通过 notifyEncodeAndRelease()
通知成功。
我们来看看 decodeFromData()
方法:
Java
private <Data> Resource<R> decodeFromData(
DataFetcher<?> fetcher, Data data, DataSource dataSource) throws GlideException {
try {
if (data == null) {
return null;
}
long startTime = LogTime.getLogTime();
Resource<R> result = decodeFromFetcher(data, dataSource);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Decoded result " + result, startTime);
}
return result;
} finally {
fetcher.cleanup();
}
}
接着往下调用 decodeFromFetcher()
方法:
Java
private <Data> Resource<R> decodeFromFetcher(Data data, DataSource dataSource)
throws GlideException {
LoadPath<Data, ?, R> path = decodeHelper.getLoadPath((Class<Data>) data.getClass());
return runLoadPath(data, dataSource, path);
}
上面的 DecodeHelper#getLoadPath()
方法就是查找到一个能够处理当前 data
的 LoadPath
,它其中封装了对应的 Decoder
和 Transcoder
等等关键组件,我们继续追踪 runLoadPath()
这个方法:
Java
private <Data, ResourceType> Resource<R> runLoadPath(
Data data, DataSource dataSource, LoadPath<Data, ResourceType, R> path)
throws GlideException {
Options options = getOptionsWithHardwareConfig(dataSource);
DataRewinder<Data> rewinder = glideContext.getRegistry().getRewinder(data);
try {
// ResourceType in DecodeCallback below is required for compilation to work with gradle.
return path.load(
rewinder, options, width, height, new DecodeCallback<ResourceType>(dataSource));
} finally {
rewinder.cleanup();
}
}
上面的 DataRewinder
就是用来让我们的 InputStream
可以重置,然后重复读取的处理对象。通过 LoadPath#load()
就能够获取到最后的结果了,这个他还添加了一个 DecodeCallback
回调。
Java
@NonNull
@Override
public Resource<Z> onResourceDecoded(@NonNull Resource<Z> decoded) {
return DecodeJob.this.onResourceDecoded(dataSource, decoded);
}
这个时候会回调 DecodeJob#onResourceDecoded()
方法,这个方法中会处理磁盘缓存的写入。我们讲网络加载的时候再介绍这个方法。
然后再看看 notifyEncodeAndRelease()
加载资源成功后的处理:
Java
private void notifyEncodeAndRelease(
Resource<R> resource, DataSource dataSource, boolean isLoadedFromAlternateCacheKey) {
GlideTrace.beginSection("DecodeJob.notifyEncodeAndRelease");
try {
// ...
notifyComplete(result, dataSource, isLoadedFromAlternateCacheKey);
// ...
} finally {
GlideTrace.endSection();
}
}
然后继续调用 notifyComplete()
方法,我注释掉了缓存相关的代码,后续讲网络的时候再看。
Java
private void notifyComplete(
Resource<R> resource, DataSource dataSource, boolean isLoadedFromAlternateCacheKey) {
setNotifiedOrThrow();
callback.onResourceReady(resource, dataSource, isLoadedFromAlternateCacheKey);
}
这个 callback
就是 EngineJob
,我们来看看它的 onResourceReady()
方法:
Java
@Override
public void onResourceReady(
Resource<R> resource, DataSource dataSource, boolean isLoadedFromAlternateCacheKey) {
synchronized (this) {
this.resource = resource;
this.dataSource = dataSource;
this.isLoadedFromAlternateCacheKey = isLoadedFromAlternateCacheKey;
}
notifyCallbacksOfResult();
}
继续看看 notifyCallbacksOfResult()
方法:
Java
void notifyCallbacksOfResult() {
ResourceCallbacksAndExecutors copy;
Key localKey;
EngineResource<?> localResource;
synchronized (this) {
stateVerifier.throwIfRecycled();
if (isCancelled) {
resource.recycle();
release();
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, key, resourceListener);
hasResource = true;
copy = cbs.copy();
incrementPendingCallbacks(copy.size() + 1);
localKey = key;
localResource = engineResource;
}
engineJobListener.onEngineJobComplete(this, localKey, localResource);
for (final ResourceCallbackAndExecutor entry : copy) {
entry.executor.execute(new CallResourceReady(entry.cb));
}
decrementPendingCallbacks();
}
EngineJobListener
就是直接通知 Engine
,对应的 Entry
就是通知 SingleRequest
。
我们先来看看 Engine#onEngineJobComplete()
是怎么处理的:
Java
@Override
public synchronized void onEngineJobComplete(
EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
// A null resource indicates that the load failed, usually due to an exception.
if (resource != null && resource.isMemoryCacheable()) {
activeResources.activate(key, resource);
}
jobs.removeIfCurrent(key, engineJob);
}
这里做两件事情,把当前的资源添加到 ActiveResources
中去,也就是存活的内存资源,前面讲过,然后把当前任务从 jobs
中移除。
我们继续看看 SingleRequest#onResourceReady()
:
Java
@Override
public void onResourceReady(
Resource<?> resource, DataSource dataSource, boolean isLoadedFromAlternateCacheKey) {
stateVerifier.throwIfRecycled();
Resource<?> toRelease = null;
try {
synchronized (requestLock) {
loadStatus = null;
if (resource == null) {
GlideException exception =
new GlideException(
"Expected to receive a Resource<R> with an "
+ "object of "
+ transcodeClass
+ " inside, but instead got null.");
onLoadFailed(exception);
return;
}
Object received = resource.get();
if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) {
toRelease = resource;
this.resource = null;
GlideException exception =
new GlideException(
"Expected to receive an object of "
+ transcodeClass
+ " but instead"
+ " got "
+ (received != null ? received.getClass() : "")
+ "{"
+ received
+ "} inside"
+ " "
+ "Resource{"
+ resource
+ "}."
+ (received != null
? ""
: " "
+ "To indicate failure return a null Resource "
+ "object, rather than a Resource object containing null data."));
onLoadFailed(exception);
return;
}
if (!canSetResource()) {
toRelease = resource;
this.resource = null;
// We can't put the status to complete before asking canSetResource().
status = Status.COMPLETE;
GlideTrace.endSectionAsync(TAG, cookie);
return;
}
onResourceReady(
(Resource<R>) resource, (R) received, dataSource, isLoadedFromAlternateCacheKey);
}
} finally {
if (toRelease != null) {
engine.release(toRelease);
}
}
}
代码都看吐了,处理很简单就是更新一下状态,然后继续向 parent
通知状态。然后还会调用 Target#onResourceReady()
方法:
我们看看 ImageViewTarget#onReousrceReady()
是怎么处理的:
Java
@Override
public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
if (transition == null || !transition.transition(resource, this)) {
setResourceInternal(resource);
} else {
maybeUpdateAnimatable(resource);
}
}
这其实就分两种情况,一种是有动画(所谓的动画也是不断修改 ImageView
中的 Drawable
而已,我就不分析了),一种是没有动画:
Java
private void setResourceInternal(@Nullable Z resource) {
// Order matters here. Set the resource first to make sure that the Drawable has a valid and
// non-null Callback before starting it.
setResource(resource);
maybeUpdateAnimatable(resource);
}
protected void setResource(@Nullable Drawable resource) {
view.setImageDrawable(resource);
}
终于把加载 RESOUCE_CACHE
的逻辑讲完了,不过后续的 DATA_CACHE
的加载与 SOURCE
的加载很多的逻辑都是和这个是一样的,我们只需要看看不同的地方就行了。
我们再来看看 DataCacheGenerator#startNext()
(DATA_CACHE
) 怎么处理的:
Java
@Override
public boolean startNext() {
GlideTrace.beginSection("DataCacheGenerator.startNext");
try {
while (modelLoaders == null || !hasNextModelLoader()) {
sourceIdIndex++;
if (sourceIdIndex >= cacheKeys.size()) {
return false;
}
Key sourceId = cacheKeys.get(sourceIdIndex);
@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;
} finally {
GlideTrace.endSection();
}
}
几乎和 ResourceCacheGenerator
中的处理方式一模一样,只是查找缓存文件的 Key
是 DataCacheKey
,他只由 ModelLoader
和 Signature
构成。而没有了前面的 with
和 height
等等参数,这也是 RESOURCE_CACHE
与 DATA_CACHE
的区别。
最后
看 Glide
的代码真的有点累,本篇文章就只看了内存缓存和磁盘缓存的逻辑都又写了这么多了,本来还要写 SOURCE
网络加载部分的,这样就只有留到下一篇文章再分析了。