Glide 源码阅览笔记(四)
在榜首篇文章中我简略介绍了Glide
实例的创建进程,重点介绍了Glide
的内存缓存完成和磁盘的缓存完成:Glide 源码阅览笔记(一)
在第二篇文章中介绍了Glide
对生命周期的管理:Glide 源码阅览笔记(二)
在第三篇文章中介绍了 Request
和 RequestCoordinator
,还简略介绍了 Registry
中的中心组件:Glide 源码阅览笔记(三)
今日的内容首要是介绍 SingleRequest
是怎样处理一个图片加载的使命。
源码阅览根据Glide 4.16.0
版本
SingleRequest
咱们在前面的文章中有提到真实完成图片使命加载的是 SingleRequest
,咱们就以 SingleRequest#begin()
办法作为开端来剖析:
@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
尺度的。
@CallSuper
@Override
public void getSize(@NonNull SizeReadyCallback cb) {
sizeDeterminer.getSize(cb);
}
持续调用 SizeDeterminer#getSize()
办法。
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
的代码:
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()
办法,咱们看看这个办法的完成:
@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()
办法是怎样处理的:
@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()
办法中详细做了什么事情:
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()
办法是怎样加载内存中的缓存的:
@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()
办法是怎样加载存活的资源的:
@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()
办法的完成:
@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()
办法便是用来收回资源目标,咱们来看看他们的完成:
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
的状况:
@VisibleForTesting
ActiveResources(
boolean isActiveResourceRetentionAllowed, Executor monitorClearedResourcesExecutor) {
this.isActiveResourceRetentionAllowed = isActiveResourceRetentionAllowed;
this.monitorClearedResourcesExecutor = monitorClearedResourcesExecutor;
monitorClearedResourcesExecutor.execute(
new Runnable() {
@Override
public void run() {
cleanReferenceQueue();
}
});
}
咱们再来看看 cleanReferenceQueue()
办法的完成:
@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()
办法来添加引证次数。咱们来看看它:
synchronized void acquire() {
if (isRecycled) {
throw new IllegalStateException("Cannot acquire a recycled resource");
}
++acquired;
}
直接把 acquired
变量加 1。咱们再看看减少引证次数的办法 release()
的完成:
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()
办法怎样处理:
@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()
办法获取缓存中的资源:
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()
是怎样作业的:
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()
办法的代码:
public synchronized void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
GlideExecutor executor =
decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor();
executor.execute(decodeJob);
}
假如答应磁盘缓存,DecodeJob
就会被添加到 diskCacheExecutor
中履行。
咱们持续看看 DecodeJob#run()
办法:
@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()
办法,咱们持续跟进:
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()
,分别看看上面的办法:
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()
办法的完成:
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()
办法:
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()
办法:
private void reschedule(RunReason runReason) {
this.runReason = runReason;
callback.reschedule(this);
}
这个 callback
其实便是 EngineJob
,咱们再看看 EngineJob#reschedule()
:
@Override
public void reschedule(DecodeJob<?> job) {
getActiveSourceExecutor().execute(job);
}
咱们看到这儿会切换至 SourceExecutor
持续履行后续的使命。
咱们来看看 ResourceCacheGenerator#startNext()
是怎样履行加载 RESOURCE_CACHE
缓存使命的:
@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()
:
@Override
public void onDataReady(Object data) {
cb.onDataFetcherReady(
sourceKey, data, loadData.fetcher, DataSource.RESOURCE_DISK_CACHE, currentKey);
}
持续回调 DecodeJob#onDataFetcherReady()
办法:
@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()
办法:
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()
办法:
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()
办法:
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()
这个办法:
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
回调。
@NonNull
@Override
public Resource<Z> onResourceDecoded(@NonNull Resource<Z> decoded) {
return DecodeJob.this.onResourceDecoded(dataSource, decoded);
}
这个时分会回调 DecodeJob#onResourceDecoded()
办法,这个办法中会处理磁盘缓存的写入。咱们讲网络加载的时分再介绍这个办法。
然后再看看 notifyEncodeAndRelease()
加载资源成功后的处理:
private void notifyEncodeAndRelease(
Resource<R> resource, DataSource dataSource, boolean isLoadedFromAlternateCacheKey) {
GlideTrace.beginSection("DecodeJob.notifyEncodeAndRelease");
try {
// ...
notifyComplete(result, dataSource, isLoadedFromAlternateCacheKey);
// ...
} finally {
GlideTrace.endSection();
}
}
然后持续调用 notifyComplete()
办法,我注释掉了缓存相关的代码,后续讲网络的时分再看。
private void notifyComplete(
Resource<R> resource, DataSource dataSource, boolean isLoadedFromAlternateCacheKey) {
setNotifiedOrThrow();
callback.onResourceReady(resource, dataSource, isLoadedFromAlternateCacheKey);
}
这个 callback
便是 EngineJob
,咱们来看看它的 onResourceReady()
办法:
@Override
public void onResourceReady(
Resource<R> resource, DataSource dataSource, boolean isLoadedFromAlternateCacheKey) {
synchronized (this) {
this.resource = resource;
this.dataSource = dataSource;
this.isLoadedFromAlternateCacheKey = isLoadedFromAlternateCacheKey;
}
notifyCallbacksOfResult();
}
持续看看 notifyCallbacksOfResult()
办法:
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()
是怎样处理的:
@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()
:
@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()
是怎样处理的:
@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
罢了,我就不剖析了),一种是没有动画:
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
) 怎样处理的:
@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
网络加载部分的,这样就只有留到下一篇文章再剖析了。