前言
Android中加载图片的形式有很多种,网上也有很多的知名图片加载库,例如Glide、Picasso、Fresco等等,它们为我们带来的方便就不需再多言了,无论是从加载到缓存还是占位图等等都提供了简易的Api,且实现强大的功能。本系列只针对Glide4.0版本源码进行分析,提高自身阅读源码的能力,同时也是为了了解其中加载的流程以及缓存的原理,本文尽可能地截图说明结合源码解析,如有疏忽之处,还请指教。
关于作者
一个在奋斗路上的Android小生,欢迎关注,互相交流
GitHub:GitHub-ZJYWidget
CSDN博客:IT_ZJYANG
简 书:Android小Y
前情回顾
上一集已经分析了Glide中load的作用,主要涉及到RequestManager和RequestBuilder,获取了我们的资源参数和想要加载的结果类型,并已经持有了这些参数,接下来就是Glide的核心部分——加载图片,这一集将开始分析Glide的into方法,这一部分有点多,稳住!!!
剧情(Glide into 运筹帷幄)
Glide中into的使用,我们一般都是传递了一个ImageView这样子的图片加载控件,然后Glide就会帮我们把之前传进去的资源给绘制到ImageView上,表面轻描淡写,底下实则深似水。代码如下:
@NonNull
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
Util.assertMainThread();
Preconditions.checkNotNull(view);
RequestOptions requestOptions = this.requestOptions;
if (!requestOptions.isTransformationSet()
&& requestOptions.isTransformationAllowed()
&& view.getScaleType() != null) {
switch (view.getScaleType()) {
case CENTER_CROP:
requestOptions = requestOptions.clone().optionalCenterCrop();
break;
case CENTER_INSIDE:
requestOptions = requestOptions.clone().optionalCenterInside();
break;
case FIT_CENTER:
case FIT_START:
case FIT_END:
requestOptions = requestOptions.clone().optionalFitCenter();
break;
case FIT_XY:
requestOptions = requestOptions.clone().optionalCenterInside();
break;
case CENTER:
case MATRIX:
default:
// Do nothing.
}
}
return into(
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
requestOptions);
}
先是老规矩,检查下传进来的ImageView是否为空,然后可以看到获取了一个requestOptions参数,是不是很眼熟,这就是我们在使用Glide时调用apply传进来的RequestOptions,然后会获取ImageView是否有设置了ScaleType,如果有,就优先使用其ScaleType来作为最终展示的ScaleType,然后接着调用了另一个into(Target,RequestListener,RequestOptions)
,这里涉及到一个新的概念——Target,那么它有什么作用呢?跟进去buildImageViewTarget
:
@NonNull
public <X> ViewTarget<ImageView, X> buildImageViewTarget(
@NonNull ImageView imageView, @NonNull Class<X> transcodeClass) {
return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
}
可以看到调用了ImageViewTargetFactory来生成对应的ViewTarget:
/**
* A factory responsible for producing the correct type of
* {@link com.bumptech.glide.request.target.Target} for a given {@link android.view.View} subclass.
*/
public class ImageViewTargetFactory {
@NonNull
@SuppressWarnings("unchecked")
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)");
}
}
}
为何Glide要区分两种Bitmap和Drawable呢,因为ImageView加载这两个的api是不一样的,点进去可以看到它们的最明显区别:
所以,我们刚才的
buildImageViewTarget
方法的作用就是根据传进来的transcodeClass
(上一集讲Glide的load的时候有提到,这个参数代表着我们最终要以什么类型来展示),生成对应的ImageViewTarget,且可以很明显看出,这个ImageViewTarget是用来最终展示图片的,这正好对应了我们transcodeClass的作用。即通俗的讲,我们在外面调用的asBitmap、asDrawable,最终Glide会在这里根据不同的类型调用ImageView的setImageBitmap或者setImageDrawable进行展示。
解释完Target的作用,我们再回到刚才的into(Target,RequestListener,RequestOptions)
,跟进去看到:
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
@NonNull RequestOptions options) {
Util.assertMainThread();
Preconditions.checkNotNull(target);
if (!isModelSet) {
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
options = options.autoClone();
Request request = buildRequest(target, targetListener, options);
Request previous = target.getRequest();
if (request.isEquivalentTo(previous)
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
request.recycle();
// If the request is completed, beginning again will ensure the result is re-delivered,
// triggering RequestListeners and Targets. If the request is failed, beginning again will
// restart the request, giving it another chance to complete. If the request is already
// running, we can let it continue running without interruption.
if (!Preconditions.checkNotNull(previous).isRunning()) {
// Use the previous request rather than the new one to allow for optimizations like skipping
// setting placeholders, tracking and un-tracking Targets, and obtaining View dimensions
// that are done in the individual Request.
previous.begin();
}
return target;
}
requestManager.clear(target);
target.setRequest(request);
requestManager.track(target, request);
return target;
}
首先看到先是判断是否为主线程,毕竟Android不允许在子线程展示UI,然后判断Target是否为空,因为刚说了,Target是用来展示图片的,必须要有。接着判断isModelSet(是不是又很眼熟,这也是Glide的load方法时赋值的),只有先调用了Glide.with().load(),这个标志位才会为true,否则就说明没有告诉Glide你要展示的是什么资源,从而就抛出异常"You must call #load() before calling #into()"
:
接着往下看:
Request request = buildRequest(target, targetListener, options);
Request previous = target.getRequest();
if (request.isEquivalentTo(previous)
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
request.recycle();
if (!Preconditions.checkNotNull(previous).isRunning()) {
previous.begin();
}
return target;
}
requestManager.clear(target);
target.setRequest(request);
requestManager.track(target, request);
这里先是生成了一个Request对象,然后又根据Target对象拿到一个Request,接着调用了isEquivalentTo
和isSkipMemoryCacheWithCompletePreviousRequest
进行判断,我们分别看俩家伙是干嘛的:
1.isEquivalentTo
isEquivalentTo有三个具体实现,我们挑其中一个——SingleRequest进行分析:
@Override
public boolean isEquivalentTo(Request o) {
if (o instanceof SingleRequest) {
SingleRequest<?> that = (SingleRequest<?>) o;
return overrideWidth == that.overrideWidth
&& overrideHeight == that.overrideHeight
&& Util.bothModelsNullEquivalentOrEquals(model, that.model)
&& transcodeClass.equals(that.transcodeClass)
&& requestOptions.equals(that.requestOptions)
&& priority == that.priority
// We do not want to require that RequestListeners implement equals/hashcode, so we don't
// compare them using equals(). We can however, at least assert that the request listener
// is either present or not present in both requests.
&& (requestListener != null
? that.requestListener != null : that.requestListener == null);
}
return false;
}
可以看到是在对比两个Request对象的参数是否完全一致,而这些参数也都很眼熟,顾名思义,这里就不具体阐述。
2.isSkipMemoryCacheWithCompletePreviousRequest
private boolean isSkipMemoryCacheWithCompletePreviousRequest(
RequestOptions options, Request previous) {
return !options.isMemoryCacheable() && previous.isComplete();
}
这里的isMemoryCacheable其实就是判断当前Glide的缓存功能是否打开,也就是我们平时设置的RequestOption#skipMemoryCache
方法传进来的boolean值。
回到刚才生成Request的过程,上述两个条件一方面判断了是否相等,另一方面判断缓存开关是否打开,最终决定我们要使用缓存还是重新请求,那么我们从buiildRequest跟进去看,看下Request里面做了啥:
private Request buildRequest(
Target<TranscodeType> target,
@Nullable RequestListener<TranscodeType> targetListener,
RequestOptions requestOptions) {
return buildRequestRecursive(
target,
targetListener,
/*parentCoordinator=*/ null,
transitionOptions,
requestOptions.getPriority(),
requestOptions.getOverrideWidth(),
requestOptions.getOverrideHeight(),
requestOptions);
}
private Request buildRequestRecursive(
Target<TranscodeType> target,
@Nullable RequestListener<TranscodeType> targetListener,
@Nullable RequestCoordinator parentCoordinator,
TransitionOptions<?, ? super TranscodeType> transitionOptions,
Priority priority,
int overrideWidth,
int overrideHeight,
RequestOptions requestOptions) {
//....忽略部分代码
Request mainRequest =
buildThumbnailRequestRecursive(
target,
targetListener,
parentCoordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight,
requestOptions);
if (errorRequestCoordinator == null) {
return mainRequest;
}
//....忽略部分代码
}
private Request buildThumbnailRequestRecursive(
Target<TranscodeType> target,
RequestListener<TranscodeType> targetListener,
@Nullable RequestCoordinator parentCoordinator,
TransitionOptions<?, ? super TranscodeType> transitionOptions,
Priority priority,
int overrideWidth,
int overrideHeight,
RequestOptions requestOptions) {
//忽略部分代码
// Recursively generate thumbnail requests.
if(thumbnailBuilder != null){
} else if (thumbSizeMultiplier != null) {
// Base case: thumbnail multiplier generates a thumbnail request, but cannot recurse.
} else {
// Base case: no thumbnail.
return obtainRequest(
target,
targetListener,
requestOptions,
parentCoordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight);
}
}
中间省略了一些代码,可以看到,buildRequest
其实调用了buildRequestRecursive
,进而调用了buildThumbnailRequestRecursive
,最终会调用到obtainRequest
,并且传递了很多眼熟的参数,比如说加载的宽高、监听、requestOption等等,那么传到obtainRequest里面又做了什么呢?继续往下看:
private Request obtainRequest(
Target<TranscodeType> target,
RequestListener<TranscodeType> targetListener,
RequestOptions requestOptions,
RequestCoordinator requestCoordinator,
TransitionOptions<?, ? super TranscodeType> transitionOptions,
Priority priority,
int overrideWidth,
int overrideHeight) {
return SingleRequest.obtain(
context,
glideContext,
model,
transcodeClass,
requestOptions,
overrideWidth,
overrideHeight,
priority,
target,
targetListener,
requestListener,
requestCoordinator,
glideContext.getEngine(),
transitionOptions.getTransitionFactory());
}
可以看到,这里又调用了SingleRequest
的obtain
方法:
public static <R> SingleRequest<R> obtain(
Context context,
GlideContext glideContext,
Object model,
Class<R> transcodeClass,
RequestOptions requestOptions,
int overrideWidth,
int overrideHeight,
Priority priority,
Target<R> target,
RequestListener<R> targetListener,
RequestListener<R> requestListener,
RequestCoordinator requestCoordinator,
Engine engine,
TransitionFactory<? super R> animationFactory) {
@SuppressWarnings("unchecked") SingleRequest<R> request =
(SingleRequest<R>) POOL.acquire();
if (request == null) {
request = new SingleRequest<>();
}
request.init(
context,
glideContext,
model,
transcodeClass,
requestOptions,
overrideWidth,
overrideHeight,
priority,
target,
targetListener,
requestListener,
requestCoordinator,
engine,
animationFactory);
return request;
}
生成了一个SingleRequest实例,并且调用SingleRequest的init方法,而init方法里面其实就是将刚才传进来的参数赋值给SingleRequest的成员变量,所以我们刚才从buildRequest走下来这么一条线路,其实最终就是生成一个带有各种加载参数的Request实例,回到一开始的buildRequest那里:
我们刚才分析了这里面的几个点,建立了Request对象,还剩两个地方未分析,一个是Request的begin方法,一个是RequestManager的track,这两个才是重点,我们分别来看下里面的内容:
Request.begin()
我们同样只挑选SingleRequest来分析,看下SingleRequest里面的begin方法:
@Override
public void begin() {
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);
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());
}
}
可以看到,其中基本都是根据状态的判断来做一些操作,首先可以看到判断model
是否为空,这里的model正是我们上一步buildRequest
传进来的参数,即代表着我们要加载的资源,因此model等于空肯定是加载失败的,然后判断是否正在执行中,不重复执行,接着判断是否已经是加载完成状态,是的话调用onResourceReady
,上面这个过程有Target的几个关键的函数:onResourceReady、onLoadFailed、onLoadStarted,我们追朔到 ImageViewTarget(是不是有点惊喜,之前提到ImageViewTarget是用来展示图片的)里面看它们的实现:
1.onLoadFailed
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {
super.onLoadFailed(errorDrawable);
setResourceInternal(null);
setDrawable(errorDrawable);
}
@Override
public void setDrawable(Drawable drawable) {
view.setImageDrawable(drawable);
}
调用了setDrawable(errorDrawable)
,而setDrawable里面果然看到了熟悉的setImageDrawable,因此onLoadFailed作用其实就是在加载失败时展示我们的失败图。
2.onResourceReady
onResourceReady里面层层递进最终也是调用的ImageViewTarget
的onResrouceReady:
@Override
public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
if (transition == null || !transition.transition(resource, this)) {
setResourceInternal(resource);
} else {
maybeUpdateAnimatable(resource);
}
}
里面其实也是加载图片资源。
3.onLoadStarted
onLoadStarted里面最终也是调用的ImageViewTarget
的onLoadStarted:
@Override
public void onLoadStarted(@Nullable Drawable placeholder) {
super.onLoadStarted(placeholder);
setResourceInternal(null);
setDrawable(placeholder);
}
来展示我们的占位图。
到这里虽然找到了展示图片的真正地方,但是很多小伙伴会疑惑,这是最终的展示,那那请求资源的过程去哪啦?我们回到刚才的begin:
刚才分析的都是图片正在加载中或者加载完成时的状态,那些状态下都是可以直接展示的,所以剩下的就是它的加载过程了:
status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
target.getSize(this);
}
由于篇幅较长,将真正请求资源的部分抽取了出来,等下回分解。
片尾
本文只讲了Glide中into方法的一半,into帮我们建立好了展示图片所用的ImageViewTarget
,然后通过我们设立的参数(包括RequestOptions)建立了一个Request对象,在Request里面控制了不同加载状态下所对应的结果展示。比如加载失败就显示失败图,加载中就显示正在加载的占位图。而Request就是一个调度请求与展示的中间人,真正的请求从这里面的Engine
才刚刚开始。