携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第9天,点击查看活动详情
基本用法
Glide最基本的用法如下:
Glide.with(context)
.load(url)
.into(imageView)
本文就从这三个方法去学习Glide的源码。
PS:本文基于4.13.2版本的Glide源码。
废话不多说直接开始!
into
into方法是Glide实际开始工作的地方,非常复杂。我们先来看看into的代码:
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
Util.assertMainThread();
Preconditions.checkNotNull(view);
//省略一些代码
return into(
glideContext.buildImageViewTarget(view, transcodeClass),
null,
requestOptions,
Executors.mainThreadExecutor());
}
可以看到,入参是一个ImageView,返回值是一个ViewTarget。但是就基本用法而言,调用者并不会关心返回的这个ViewTarget。ViewTarget的两个泛型分别表示需要加载的目标View(必须是View的子类,这里即是ImageView)以及第二篇说到的ResourceType。
继续跟踪这个into的重载方法。
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
BaseRequestOptions<?> options,
Executor callbackExecutor) {
Preconditions.checkNotNull(target);
if (!isModelSet) {
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
Request request = buildRequest(target, targetListener, options, callbackExecutor);
Request previous = target.getRequest();
if (request.isEquivalentTo(previous)
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
if (!Preconditions.checkNotNull(previous).isRunning()) {
previous.begin();
}
return target;
}
requestManager.clear(target);
target.setRequest(request);
requestManager.track(target, request);
return target;
}
先看返回类型Y。Y是Target的子类,由一开始那个into方法可以判断出,Y应该是ViewTarget<ImageView,Drawable>,而ViewTarget确实是Target的子类。也就说在我们这个例子中,Y实际上是Target。
接着来看入参:
- Y类型的target:从上面的分析可以判断出,target的类型是Target。这个target是通过一开始那个into中生成并传入的,我们回头看看这个生成的方法:
// #ClideContext
public <X> ViewTarget<ImageView, X> buildImageViewTarget(
@NonNull ImageView imageView, @NonNull Class<X> transcodeClass) {
return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
}
这个buildImageViewTarget是GlideContext的方法,而GlideContext则是在Glide生成实例的时候生成的,里面包含了所有加载资源所需的注册表和类(Engine、默认参数。一些工厂类等)。
通过上面的分析我们可以推测出这个X泛型的实际类型为Drawable。再看看ImageViewTargetFactory如何生产ViewTarget。
public class ImageViewTargetFactory {
public <Z> ViewTarget<ImageView, Z> buildTarget(
@NonNull ImageView view, @NonNull Class<Z> clazz) {
if (Bitmap.class.equals(clazz)) {
return (ViewTarget<ImageView, Z>) new BitmapImageViewTarget(view);
} else if (Drawable.class.isAssignableFrom(clazz)) {
return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);
} else {
throw new IllegalArgumentException(
"Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)");
}
}
}
同理这个Z泛型也是Drawable(这Glide真的多泛型……)。代码很简单,判断Z是Bitmap还是Drawable,分别返回BitmapImageViewTarget和DrawableImageViewTarget。
由于我们这里的例子是Drawable,那自然生产出来的是DrawableImageViewTarget。(这里要关注一下Drawable.class.isAssignableFrom(clazz)这个判断条件,只要clazz是Drawable的子类就返回true)
说完了target,继续说into的其他入参!
- targetListeners:传空不管了。
- options:配置Request的options。
- callbackExecutor:回调的Executor,这里传入了主线程Executor,也就是回调将会在主线程调用。
看完了返回和入参,我们继续阅读一下into的代码,这里重贴一遍:
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
BaseRequestOptions<?> options,
Executor callbackExecutor) {
Preconditions.checkNotNull(target);
if (!isModelSet) {
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
Request request = buildRequest(target, targetListener, options, callbackExecutor);
Request previous = target.getRequest();
if (request.isEquivalentTo(previous)
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
if (!Preconditions.checkNotNull(previous).isRunning()) {
previous.begin();
}
return target;
}
requestManager.clear(target);
target.setRequest(request);
requestManager.track(target, request);
return target;
}
一行行看!
- 检查target是否非空。
- 未调用load不允许调用into,都不知道去哪获取那肯定是不能执行获取啦!
- 构建Request。
- 判断构建的request是不是与当前的request(即代码中的previous,实际上这时候构建出来的request并没有设到target中,所以previous其实是“当前的”request)相同,并且当前request还没完成并打开了内存缓存,并且当前request没有在运行,则直接调用当前request.begin。也就是说在打开了内存缓存的情况下,执行相同的request会忽略掉新执行的request。这是request的重用优化策略。
- 假如确实是一个新的request,则清除当前的request。
- 将新request设入target。
- requestManager跟踪target以及新request。
- 返回target。
乍一看,request也没有被执行啊?点开了唯一有可能执行request的地方——requestManager.track。
synchronized void track(@NonNull Target<?> target, @NonNull Request request) {
targetTracker.track(target);
requestTracker.runRequest(request);
}
果不其然,runRequest藏在了这里!吐槽一下,track就是track,run就是run,把run藏在track里实属让人头大。
到了这里,我觉得应该要解释一下Target与Request之间的关系。
- Target:Glide加载图片后送达的目的地,可以理解为结果回调。并且继承了Glide生命周期的监听接口(LifecycleListener),这也意味着Target拥有能感知生命周期的能力。
- Request:负责请求的类。
至于两者的关系,可谓是有点错综复杂。首先一句话总结:两者在一定程度上互相持有。为什么说是一定程度上呢?因为在ViewTarget(即需要加载图片的是一个View时)的子类里,Target对Request的持有是通过View.setTag的方式间接持有的;而在非ViewTarget的Target中,则是以成员变量的方式直接持有的。Request则是直接以成员变量的方式持有Target的。其实在Request接口的定义里面,是找不到和Target有关联的蛛丝马迹的,但是所有的Request实际上执行请求的都是SingleRequest这个实现类,而在这个实现类的成员变量里,就有着Target,持有Target的目的是为了能回调请求的生命周期。(这里其实我有点疑惑,既然所有的请求都得回调生命周期给Target,为什么不在Request接口定义的时候就把Target相关的东西也定义好呢?这样不会减少错误使用的几率吗?)
classDiagram
class Target{
+setRequest(Request)
+getRequest()
}
class BaseTarget
class ViewTarget{
+View view
}
class ImageViewTarget
class DrawableImageViewTarget
class BitmapImageViewTarget
class Request
class SingleRequest
<<interface>> Target
<<interface>> Request
<<abstract>> BaseTarget
<<abstract>> ViewTarget
<<abstract>> ImageViewTarget
Target <|.. BaseTarget
BaseTarget <|.. ViewTarget
ViewTarget <|-- ImageViewTarget
ImageViewTarget <|-- DrawableImageViewTarget
ImageViewTarget <|-- BitmapImageViewTarget
Request o--o Target
Request <|.. SingleRequest
说完Target与Request之间的关系,再来说说TargetTracker以及RequestTracker吧!
- TargetTracker:RequestManager中,用来管理当前活跃的Target集合的类。实现了Glide生命周期监听接口(LifecycleListener)并持有当前活跃的全部Target(以Set储存)。当Glide生命周期事件被触发时,TargetTracker负责遍历所有活跃的Target并传递相关的生命周期事件。
- RequestTracker:负责管理(追踪、取消、重试)各种状态(正在执行、已完成、失败)的Request。Glide具有生命周期感知能力并能自行暂停、恢复以及取消任务的能力,正是在RequestTracker中实现的。RequestTracker中维护了两个集合:requests——所有Request集合;pendingRequests——处于暂停状态的Request集合。当需要暂停请求时,将requests集合中正在执行中的Request暂停并放入pendingRequests集合中;恢复请求时,则将pendingRequests集合中的Request重新执行,然后清空pendingRequests集合。值得注意的是,requests集合是一个弱引用集合。弱引用肯定是为了避免内存泄漏的,但是为什么会出现内存泄漏我想不通,如果有兄弟姐妹知道希望不吝赐教。
让我们再次把目光移回到RequestTracker.runRequest方法中。
public void runRequest(@NonNull Request request) {
requests.add(request);
if (!isPaused) {
request.begin();
} else {
request.clear();
pendingRequests.add(request);
}
}
做的事情也很简单,把传入的request放入requests集合中,并调用request的begin方法(只讨论非暂停状态)。
由于Request是个接口,如果要看实际的操作,那就要找到Request的实现类。虽然上面已经剧透了实际上的请求都是SingleRequest这个实现类,但还是探究一下这个SingleRequest是如何构建出来的。让我们回到into方法里面的构建Request的代码中,也就是buildRequest方法,该方法简单地调用了buildRequestRecursive。
private Request buildRequestRecursive(
Object requestLock,
Target<TranscodeType> target,
@Nullable RequestListener<TranscodeType> targetListener,
@Nullable RequestCoordinator parentCoordinator,
TransitionOptions<?, ? super TranscodeType> transitionOptions,
Priority priority,
int overrideWidth,
int overrideHeight,
BaseRequestOptions<?> requestOptions,
Executor callbackExecutor) {
//①
ErrorRequestCoordinator errorRequestCoordinator = null;
if (errorBuilder != null) {
errorRequestCoordinator = new ErrorRequestCoordinator(requestLock, parentCoordinator);
parentCoordinator = errorRequestCoordinator;
}
//②
Request mainRequest =
buildThumbnailRequestRecursive(
requestLock,
target,
targetListener,
parentCoordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight,
requestOptions,
callbackExecutor);
if (errorRequestCoordinator == null) {
return mainRequest;
}
//③
int errorOverrideWidth = errorBuilder.getOverrideWidth();
int errorOverrideHeight = errorBuilder.getOverrideHeight();
if (Util.isValidDimensions(overrideWidth, overrideHeight) && !errorBuilder.isValidOverride()) {
errorOverrideWidth = requestOptions.getOverrideWidth();
errorOverrideHeight = requestOptions.getOverrideHeight();
}
Request errorRequest =
errorBuilder.buildRequestRecursive(
requestLock,
target,
targetListener,
errorRequestCoordinator,
errorBuilder.transitionOptions,
errorBuilder.getPriority(),
errorOverrideWidth,
errorOverrideHeight,
errorBuilder,
callbackExecutor);
errorRequestCoordinator.setRequests(mainRequest, errorRequest);
return errorRequestCoordinator;
}
老规矩先看入参:
- requestLock:用于加锁的对象,传入了一个new Object()。
- target:本例中就是DrawableImageViewTarget。
- targetListener:一开始传入为null,则这里也为null。
- parentCoordinator:传入了null。
- transitionOptions:对资源进行处理的配置项。
- priority:请求的优先级。
- overrideWidth:将资源resize的宽度。
- overrideHeight:将资源resize的高度。
- requestOptions:自定义配置项。
- callbackExecutor:回调的Executor,本例中为主线程。
接下来看代码,主要分成三个部分:
- ①处代码。如果errorBuilder不为空,初始化一个ErrorRequestCoordinator。这里浅浅解释一下,其实在本例的基本用法中,是不会出现这个ErrorRequestCoordinator的。ErrorRequestCoordinator本质上也是一个Request,里面持有两个Request:一个正常的Request,一个错误Request。当正常Request出现加载失败时候,就会自动执行ErrorRequest,加载指定的错误资源。但没调用过RequestBuilder.error的话是没有这个errorBuilder的。
- ②处代码。构建一个mainRequest并返回该Request。(不考虑设置了errorBuilder的情况)
- ③处代码。这里就是设置了errorBuilder的情况下,会将mainRequest作为正常Request传入ErrorRequestCoordinator中,返回ErrorRequestCoordinator。
自然②处代码是关键地方,继续看看构建mainRequest:
private Request buildThumbnailRequestRecursive(
//入参忽略
) {
if (thumbnailBuilder != null) {
//设置了thumbnailBuilder,会生成一个ThumbnailRequestCoordinator,参考ErrorRequestCoordinator,本例省略。
} else if (thumbSizeMultiplier != null) {
//设置了thumbSizeMultiplier,同样是生成ThumbnailRequestCoordinator。省略。
} else {
//没设置thumbnail,本例情况。
return obtainRequest(
requestLock,
target,
targetListener,
requestOptions,
parentCoordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight,
callbackExecutor);
}
}
由于本例没有设置thumbnail(缩略图),所以直接就是走到obtainRequest,obtainRequest调用了SingleRequest.obtain,进而调用SingleRequest的构造方法,没啥知识点,这里就不看了,知道获取到了SingleRequest就ok了。
还记得RequestTracker的runRequest方法吗?调用了Request.begin。而通过obtainRequest获取到的Request实际上是SingleRequest,那我们就直接看SingleRequest的begin方法吧!
public void begin() {
synchronized (requestLock) {
//①
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;
}
//②
if (status == Status.RUNNING) {
throw new IllegalArgumentException("Cannot restart a running request");
}
//③
if (status == Status.COMPLETE) {
onResourceReady(
resource, DataSource.MEMORY_CACHE, false);
return;
}
//④
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());
}
}
}
PS:上述代码经过删减。
在阅读这部分代码之前,首先看一下SingleRequest的状态枚举类Status的状态流转。
stateDiagram
[*] --> PENDING : 初始化
PENDING --> WAITING_FOR_SIZE : begin()
WAITING_FOR_SIZE --> RUNNING : onSizeReady()
RUNNING --> COMPLETE : onResourceReady()
RUNNING --> FAILED : onLoadFailed()
PENDING --> FAILED : model == null
COMPLETE --> [*]
FAILED --> [*]
图很简单,就不多做解释了。(PS:实际上的Status还有一个CLEARED,但是为了保持图的简洁明了就删掉了,问题也不大,我们只关注主要流程)
现在开始逐个部分来看begin的代码:
- ①处代码。如果model为空直接调用onLoadFailed。model在本例中则为url。
- ②处代码。如果该Request正在执行则抛出异常。
- ③处代码。将Status设为WAITING_FOR_SIZE。假如设定了overrideSize则直接使用overrideSize并调用onSizeReady;反之调用Target的getSize异步计算Size,Target计算完成后会调用Request的onSizeReady。
- ④处代码。如果Status为RUNNING(对应③处代码指定了overrideSize的情况)或WAITING_FOR_SIZE(对应异步Size的情况)并且可以通知状态的情况下,通知Target开始加载资源(onLoadStarted)。
由上图状态图可以看到,WAITING_FOR_SIZE经过onSizeReady后就会转入RUNNING,那就继续看看onSizeReady。
public void onSizeReady(int width, int height) {
synchronized (requestLock) {
if (status != Status.WAITING_FOR_SIZE) {
return;
}
status = Status.RUNNING;
float sizeMultiplier = requestOptions.getSizeMultiplier();
this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
this.height = maybeApplySizeMultiplier(height, sizeMultiplier);
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;
}
}
}
这里的代码也是非常简单,计算一下宽高,然后调用engine.load方法。
说到这个Engine,是整个Glide中承担请求、解码、缓存、变换的核心部分。它在Glide中也是以单例的形式存在,它伴随着Glide单例初始化而初始化。
接下来看看Engine.load。
public <R> LoadStatus load(
//由于入参太多,这里删除一下。
) {
//①
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, false);
return null;
}
- ①处代码。根据传入的各种参数生成一EngineKey,这个EngineKey是用于判断Request是不是相同,并且是缓存该Request资源的凭证(由于储存资源的容器是Map,所以该凭证也即是Map的Key)。
- ②处代码。从内存缓存中根据生成的Key寻找对应的Resource。
- ③处代码。如果不存在于内存缓存中,则试图从正在执行的任务中获取Resource或开始一个新的获取任务(即无对应的正在执行的任务)并返回该任务的LoadState。
- ④处代码。如果存在内存缓存,则直接调用onResourceReady返回Resource并指定该结果为内存缓存。返回一个空的LoadState。
本例假设没有存在内存缓存,即走到③处代码中。接下来看看waitForExistingOrStartNewJob。
private <R> LoadStatus waitForExistingOrStartNewJob(
//入参太多,删除掉
) {
//①
EngineJob<?> current = jobs.get(key, onlyRetrieeFromCache);
//②
if (current != null) {
current.addCallback(cb, callbackExecutor);
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);
return new LoadStatus(cb, engineJob);
}
这段代码跟上一段的整体逻辑也是差不多。
- ①处代码。根据EngineKey从jobs(Jobs持有以EngineKey为键,EngineJob为值的Map,缓存着正在进行的Job)检索对应的Job。这里可以看出,无论是Resource还是EngineJob,都是以EngineKey作为唯一凭证的。
- ②处代码。如果从jobs中检索到对应的Job,则直接将cb(ResourceCallback,本例中即为SingleRequest)设入检索到的job中,并返回LoadState。因为LoadState只涉及取消请求,所以这里不展开了。
- ③处代码。没有检索到对应的Job,则生成新的Job。这里又两个Job:EngineJob以及DecodeJob。DecodeJob负责将EngineJob请求回来的数据进行解码,也就是DecodeJob是EngineJob的一个组件。创建好了EngineJob,将其放入jobs中,设置好cd后调用EngineJob.start启动任务,最后返回LoadState。
至此,终于开始要真正请求和处理图片了,由于篇幅问题,后面的解析会再开一篇。本来是想着一个方法出一篇的,可是没想到这个into这么复杂。继续老规矩,整理一个图总结一下。
sequenceDiagram
RequestBuilder ->> RequestBuilder :into()
RequestBuilder ->> ReqestManager:buildRequest()
ReqestManager ->> RequestTracker:track()
RequestTracker ->> SingleRequest:runRequest()
SingleRequest ->> SingleRequest:onSizeReady()
SingleRequest ->> Engine:load()
Engine ->> EngineJob:waitForExistingOrStartNewJob()
EngineJob ->> EngineJob:start()