Glide遇坑记之背景
Glide是一个专注于平滑滚动和图片加载的缓存库。Glide提供了易用的API,高性能、可扩展的图片解码管道,以及自动的资源池技术。
在现如今有着众多的图片加载库Android-Universal-ImageLoader,Glide,Fresco,Picasso的背景之下,Glide依旧屹立不倒在于:
- Glide不仅是一个图片缓存,它支持 Gif、缩略图,甚至是 Video。并支持拉取,解码和展示视频快照,所以更该当做一个媒体缓存。
- 与Activity/Fragment生命周期一致,支持trimMemory。Glide 对每个 context 都保持一个 RequestManager,通过 FragmentTransaction 保持与 Activity/Fragment 生命周期一致,并且有对应的 trimMemory 接口实现可供调用。
- 友好的内存缓存设计。Glide从内存中读取数据时,不像一般的实现用 get,而是用 remove,再将这个缓存数据放到一个 value 为软引用的 activeResources map 中,并计数引用数,在图片加载完成后进行判断,如果引用计数为空则回收掉。
- Glide的Bitmap格式默认使用 RGB_565 ,虽然清晰度稍差,但内存开销要比ARGB_8888格式小一半。
- Glide易用的API,使开发者可以插入和替换成自己喜爱的任何网络栈。默认情况下,Glide使用的是一个定制化的基于HttpUrlConnection的栈,但同时也提供了与Google Volley和Square OkHttp快速集成的工具库。
API
Glide 使用简明的流式语法API,允许你在大部分情况下一行代码搞定需求
Glide.with(context).load(url).into(imageView);
性能
Glide 充分考虑了Android图片加载性能的两个关键方面
- 图片解码速度
- 解码图片带来的资源压力
为了让用户拥有良好的App使用体验,图片不仅要快速加载,而且还不能因为过多的主线程I/O或频繁的垃圾回收导致页面的闪烁和抖动现象。Glide使用了多个步骤来确保在Android上加载图片尽可能的快速和平滑:
- 自动、智能地下采样(downsampling)和缓存(caching),以最小化存储开销和 解码次数;
- 积极的资源重用,例如字节数组和Bitmap,以最小化昂贵的垃圾回收和堆碎片影响;
- 深度的生命周期集成,以确保仅优先处理活跃的Fragment和Activity的请求,并有利于应用在必要时释放资源以避免在后台时被杀掉。
Glide遇坑记之阐述
在使用Glide中加载图片的过程中,第一次只显示占位符placeHolder,但之后从内存或磁盘中加载图片则可正常显示。问题仅会出现在使用CircleImageView与placeHolder,且在进行网络图片加载的前提之下。
对于此问题有以下解决方法
- 不设置占位符
placeHolder
- 使用
dontAnimation
方法禁止认动画 - 抛弃
CircleImageView
,使用ImageView
- 使用
Glide
的Transformation
API自定义圆形图片
Glide遇坑记之分析
通过上面的问题阐述和解决方法我们可以进行推测,问题与Glide的占位符、默认动画、缓存机制,并与CircleImageView存在一定的关联。为了探究问题出现在何方,我们先从Glide的源码入手,尝试探究图片加载具体实现逻辑,来查明问题所在。
Glide.with(context).load(url).into(imageView);
Glide源码分析(一)
- Glide.with(context)
public static RequestManager with(Context context) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(context);
}
Glide有四个静态的重载方法with()
,其内部都是先调用RequestManagerRetriever
的静态 get()
方法获取一个RequestManagerRetriever
对象。再根据传入with()
方法的参数调用RequestManagerRetriever
的实例get()
方法,获取RequestManager
对象。
public RequestManager get(FragmentActivity activity) {
if (Util.isOnBackgroundThread()) {
return get(activity.getApplicationContext());
} else {
assertNotDestroyed(activity);
FragmentManager fm = activity.getSupportFragmentManager();
return supportFragmentGet(activity, fm);
}
}
RequestManager supportFragmentGet(Context context, FragmentManager fm) {
SupportRequestManagerFragment current = getSupportRequestManagerFragment(fm);
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
current.setRequestManager(requestManager);
}
return requestManager;
}
不管你在Glide.with()
方法中传入的是Activity
、FragmentActivity
、v4
包下的Fragment
、还是app
包下的Fragment
,最终的流程都是,通过向当前的Activity
当中添加一个隐藏的Fragment
,将Glide的加载请求与Activity/Fragment
的生命周期绑定而自动执行请求,暂停操作。
- RequestManager.load(url)
public DrawableTypeRequest<String> load(String string) {
return (DrawableTypeRequest<String>) fromString().load(string);
}
先调用fromString()
方法,再调用load()
方法,然后把传入的图片URL地址传进去。而fromString()
方法也仅是调用了loadGeneric()
方法,并且指定参数为String.class
,因为load()
方法参数为字符串。
private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass) {
ModelLoader<T, InputStream> streamModelLoader = Glide.buildStreamModelLoader(modelClass, context);
ModelLoader<T, ParcelFileDescriptor> fileDescriptorModelLoader =Glide.buildFileDescriptorModelLoader(modelClass, context);
if (modelClass != null && streamModelLoader == null && fileDescriptorModelLoader == null) {
throw new IllegalArgumentException("");
}
return optionsApplier.apply(new DrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context, glide, requestTracker, lifecycle, optionsApplier));
}
loadGeneric()
方法返回一个DrawableTypeRequest
对象,在loadGeneric()
方法中利用获得的ModelLoader
对象和其他参数new了一个DrawableTypeRequest
对象,然后调用DrawableTypeRequestBuilder
的load方法。load()
方法的具体逻辑都是在DrawableRequestBuilder
的父类GenericRequestBuilder
当中,load()
方法仅是对一些简单变量进行相应的赋值操作。DrawableRequestBuilder
中有多个方法,这些方法就是Glide绝大多数的API了。
- GenericRequestBuilder.into(imageView)
@Override
public Target<GlideDrawable> into(ImageView view) {
return super.into(view);
}
into()
方法的具体逻辑都是在DrawableRequestBuilder
的父类GenericRequestBuilder
当中
public Target<TranscodeType> into(ImageView view) {
Util.assertMainThread();
if (!isTransformationSet && view.getScaleType() != null) {
switch (view.getScaleType()) {
case CENTER_CROP:
applyCenterCrop();
break;
case FIT_CENTER:
case FIT_START:
case FIT_END:
applyFitCenter();
break;
default:
}
}
return into(glide.buildImageViewTarget(view, transcodeClass));
}
GenericRequestBuilder
提供多个重载的into()
方法,ImageView
参数、Target
参数、int
参数。最后一行代码先是调用了glide.buildImageViewTarget()
方法,这个方法会构建出一个Target
对象,Target
对象则是用来最终展示图片用的。
<R> Target<R> buildImageViewTarget(ImageView imageView, Class<R> transcodedClass) {
return imageViewTargetFactory.buildTarget(imageView, transcodedClass);
}
调用了ImageViewTargetFactory
的buildTarget()
方法
public <Z> Target<Z> buildTarget(ImageView view, Class<Z> clazz) {
if (GlideDrawable.class.isAssignableFrom(clazz)) {
return (Target<Z>) new GlideDrawableImageViewTarget(view);
} else if (Bitmap.class.equals(clazz)) {
return (Target<Z>) new BitmapImageViewTarget(view);
} else if (Drawable.class.isAssignableFrom(clazz)) {
return (Target<Z>) new DrawableImageViewTarget(view);
} else {
throw new IllegalArgumentException("");
}
}
buildTarget()
方法中会根据传入的class
参数来构建不同的Target
对象。默认情况下DrawableTypeRequest
在构造函数调用super()
方法中向父类中的transcodeClass
变量传递GlideDrawable.class
参数,buildTarget()
方法利用ImageView
构建的GlideDrawableImageViewTarget
对象。如果你在链式调用之中使用asBitmap()
方法,BitmapTypeRequest
则在构造函数中向父类传递Bitmap.class
参数,那么buildTarget()
方法则利用ImageView
构建出BitmapImageViewTarget
对象。通过 glide.buildImageViewTarget()
方法,我们构建出了一个GlideDrawableImageViewTarget
对象。回到into()
方法的最后一行,可以看到,这里又将Target
对象传入到了GenericRequestBuilder
另一个接收Target
对象的into()
方法当中。
public <Y extends Target<TranscodeType>> Y into(Y target) {
Util.assertMainThread();
Request previous = target.getRequest();
if (previous != null) {
previous.clear();
requestTracker.removeRequest(previous);
previous.recycle();
}
Request request = buildRequest(target);
target.setRequest(request);
lifecycle.addListener(target);
requestTracker.runRequest(request);
return target;
}
这个重载的into()
方法利用传入的Target
对象和buildRequest()
方法来构建Request
对象。buildRequest()
方法内部通过调用buildRequestRecursive
来构建一个GenericRequest
对象。在通过buildRequest
创建请求成功后,使用了target.setRequest(request)
将请求设置到target
,并通过addListener()
方法将Target
加入到Lifecycle
。上面执行了那么多都只是请求创建,请求的执行时通过requestTracker.runRequest(request)
开始的。
private Request buildRequestRecursive(Target<TranscodeType> target, ThumbnailRequestCoordinator parentCoordinator) {
if (thumbnailRequestBuilder != null) {
ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);
Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator);
Request thumbRequest = thumbnailRequestBuilder.buildRequestRecursive(target, coordinator);
coordinator.setRequests(fullRequest, thumbRequest);
return coordinator;
} else if (thumbSizeMultiplier != null) {
// Base case: thumbnail multiplier generates a thumbnail request, but cannot recurse.
ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);
Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator);
Request thumbnailRequest = obtainRequest(target, thumbSizeMultiplier, getThumbnailPriority(), coordinator);
coordinator.setRequests(fullRequest, thumbnailRequest);
return coordinator;
} else {
// Base case: no thumbnail.
return obtainRequest(target, sizeMultiplier, priority, parentCoordinator);
}
}
buildRequestRecursive()
方法中大部分代码都是在处理缩略图
,buildRequestRecursive()
中调用obtainRequest()
方法来获取一个发送图片加载请求的GenericRequest
对象。
private Request obtainRequest(Target<TranscodeType> target, float sizeMultiplier, Priority priority,RequestCoordinator requestCoordinator) {
return GenericRequest.obtain(
loadProvider,
model,
signature,
context,
priority,
target,
sizeMultiplier,
placeholderDrawable,
placeholderId,
errorPlaceholder,
errorId,
fallbackDrawable,
fallbackResource,
requestListener,
requestCoordinator,
glide.getEngine(),
transformation,
transcodeClass,
isCacheable,
animationFactory,
overrideWidth,
overrideHeight,
diskCacheStrategy);
}
obtain()
方法需要传入非常多的参数,而其中很多的参数我们都是比较熟悉的,像placeholderId
、errorPlaceholder
、diskCacheStrategy
等。load()
方法中调用的所有API,在这里组装到Request
对象当中。
obtainRequest()
方法内部调用GenericRequest
的obtain()
方法,GenericRequest
的obtain()
方法实际上获得的就是一个GenericRequest
对象。另外在GenericRequest
的obtain()
方法内部又调用了GenericRequest
的 init()
方法,init()
方法里面主要就是一些赋值的代码,将传入的这些参数赋值到GenericRequest
的成员变量当中。
现在解决了构建Request
对象的问题,接下来我们看一下这个Request
对象又是怎么执行的。其实是在into()
方法中通过调用requestTracker.runRequest()
方法来去执行这个Request
。
public void runRequest(Request request) {
requests.add(request);
if (!isPaused) {
request.begin();
} else {
pendingRequests.add(request);
}
}
在不为暂停状态下调用Request
的begin()
方法来执行Request
开始执行请求,否则的话就先将Request
添加到待执行队列
里面,等暂停状态解除之后再执行。
@Override
public void begin() {
startTime = LogTime.getLogTime();
if (model == null) {
onException(null);
return;
}
status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
target.getSize(this);
}
if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {
target.onLoadStarted(getPlaceholderDrawable());
}
}
在begin()
方法中进行具体图片加载操作。这里要分两种情况,一种是你使用了override()
API为图片指定了一个固定的宽高,一种是没有指定。如果指定了的话,就会调用onSizeReady()
方法。如果没指定的话,就会用target.getSize()
方法。这个target.getSize()
方法的内部会根据ImageView
的layout_width
和layout_height
值做一系列的计算,来算出图片应该的宽高,在计算完成之后它也会调用onSizeReady()
方法。也就是说,不管是哪种情况,最终都会调用到onSizeReady()
方法,在这里通过Engine
及相关类完成网络请求和图片获取之后,最终会回调至GenericRequest
的onResourceReady()
方法之中。篇幅有限,网络请求部分不为重点,对此不再进行分析。
@Override
public void onLoadStarted(Drawable placeholder) {
view.setImageDrawable(placeholder);
}
在发起图片加载请求之后,通过!isComplete() && !isFailed() && canNotifyStatusChanged()
对Glide请求状态进行判断,若加载请求正常进行则调用target
的onLoadStarted
方法,从而在加载前显示占位图,当然设置加载错误图片占位图的原理也是一样,只不过回调执行时机不同。
以上,我们对Glide的源码从 with()
到load()
、再是into()
有了一个大致的了解,下面我们将带着问题继续探究在获取到图片之后Glide是如何通过onResourceReady()
方法显示到ImageView
之中。
Glide源码分析(二)
通过问题分析及对Glide源码的解读,占位符、默认动画、缓存机制都对animate()
方法在不同方面产生影响。
public final DrawableRequestBuilder<ModelType> crossFade() {
super.animate(new DrawableCrossFadeFactory<GlideDrawable>());
return this;
}
DrawableRequestBuilder
在构造函数中默认调用crossFade()
函数,开启渐显动画效果。crossFade()
方法的具体实现逻辑都是在DrawableRequestBuilder
的父类GenericRequestBuilder
的实例方法animate()
当中。
GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> animate(GlideAnimationFactory<TranscodeType> animationFactory) {
if (animationFactory == null) {
throw new NullPointerException("Animation factory must not be null!");
}
this.animationFactory = animationFactory;
return this;
}
crossFade()
方法主要是通过调用animation()
对animationFactory
变量进行赋值操作。赋值之后animationFactory
变量含有DrawableCrossFadeFactory
对象的引用。
public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> dontAnimate() {
GlideAnimationFactory<TranscodeType> animation = NoAnimation.getFactory();
return animate(animation);
}
dontAnimate()
方法内部通过调用获取一个对象,并传入,animationFactory
变量则会指向NoAnimationFactory
对象。
回到GenericRequest.onResourceReady()
方法。
private void onResourceReady(Resource<?> resource, R result) {
boolean isFirstResource = isFirstReadyResource();
status = Status.COMPLETE;
this.resource = resource;
if (requestListener == null || !requestListener.onResourceReady(result, model, target,loadedFromMemoryCache,isFirstResource)) {
GlideAnimation<R> animation = animationFactory.build(loadedFromMemoryCache, isFirstResource);
target.onResourceReady(result, animation);
}
notifyLoadSuccess();
}
在onResourceReady()
方法中先是调用resource.get()
方法获取封装图片的GlideDrawable
对象,接着调用另一重载的onResourceReady()
方法之中的animationFactory.build()
方法并传入是否来自内存缓存
、是否首次加载
两个布尔值来获取GlideAnimation
对象。
public static class NoAnimationFactory<R> implements GlideAnimationFactory<R> {
public GlideAnimation<R> build(boolean isFromMemoryCache, boolean isFirstResource) {
return (GlideAnimation<R>) NO_ANIMATION;
}
}
NoAnimationFactory
对象简单粗暴的通过animationFactory.build()
方法直接返回一个NoAnimation
对象实例。
public GlideAnimation<T> build(boolean isFromMemoryCache, boolean isFirstResource) {
if (isFromMemoryCache) {
return NoAnimation.get();
} else if (isFirstResource) {
return getFirstResourceAnimation();
} else {
return getSecondResourceAnimation();
}
}
DrawableCrossFadeFactory
对象的build()
方法内部则根据布尔值调用相应函数,并返回对应的GlideAnimation
实例。从内存中获取已缓存图片,调用NoAnimation.get()
方法获取NoAnimation
对象实例;从网络中首次获取图片,调用getFirstResourceAnimation()
方法创建并返回一个DrawableCrossFadeViewAnimation
对象实例;从网络中获取已加载图片,调用getSecondResourceAnimation()
方法创建并返回一个DrawableCrossFadeViewAnimation
对象实例
private GlideAnimation<T> getFirstResourceAnimation() {
if (firstResourceAnimation == null) {
GlideAnimation<T> defaultAnimation = animationFactory.build(false /*isFromMemoryCache*/, true /*isFirstResource*/);
firstResourceAnimation = new DrawableCrossFadeViewAnimation<T>(defaultAnimation, duration);
}
return firstResourceAnimation;
}
getFirstResourceAnimation()
方法内部先是调用animationFactory.build()
获取一个defaultAnimation
对象。这里的animationFactory
在DrawableCrossFadeFactory
的构造函数中进行赋值,是一个ViewAnimationFactory
对象实例,然后将defaultAnimation
传入构造函数获取DrawableCrossFadeViewAnimation
对象实例。
getSecondResourceAnimation()
与getFirstResourceAnimation()
方法仅是传入不同的参数从而从animationFactory.build()
方法中获取对应的对象实例。getSecondResourceAnimation()
方法通过调用animationFactory.build()
获取一个NoAnimation
对象。
public GlideAnimation<R> build(boolean isFromMemoryCache, boolean isFirstResource) {
if (isFromMemoryCache || !isFirstResource) {
return NoAnimation.get();
}
if (glideAnimation == null) {
glideAnimation = new ViewAnimation<R>(animationFactory);
}
return glideAnimation;
}
ViewAnimationFactory
的build()
方法与DrawableCrossFadeFactory
的build()
方法类似,同为获取GlideAnimation
对象实例,DrawableCrossFadeFactory
返回的对象在特定情况下(启用默认动画且未设置占位符),其实是利用ViewAnimationFactory.build()
方法所返回的ViewAnimation
或NoAnimation
对象实例来展示图片并完成动画效果。
若图片来自内存或不为首次加载则返回NoAnimation
对象,其他情况下直接返回ViewAnimation
对象。然后将对应对象实例传入DefaultAnimationFactory
对象构造函数。
回到GenericRequest.onResourceReady()
方法,继续执行,接着调用target.onResourceReady()
方法并传入GlideDrawable
与GlideAnimation
对象。这个target
又是什么呢?在into()
方法的最后一行,调用glide.buildImageViewTarget()
方法来构建出一个target
,而这个target
就是一个GlideDrawableImageViewTarget
对象。
@Override
public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> animation) {
if (!resource.isAnimated()) {
if (Math.abs(viewRatio - 1f) <= SQUARE_RATIO_MARGIN
&& Math.abs(drawableRatio - 1f) <= SQUARE_RATIO_MARGIN) {
resource = new SquaringDrawable(resource, view.getWidth());
}
}
super.onResourceReady(resource, animation);
this.resource = resource;
resource.setLoopCount(maxLoopCount);
resource.start();
}
GlideDrawableImageViewTarget
在onResourceReady()
方法中做了一些逻辑处理,包括如果是GIF图片的话,就调用resource.start()
方法开始播放图片。通过调用super.onResourceReady()
方法,将GlideDrawable
显示到ImageView
上,GlideDrawableImageViewTarget
的父类是ImageViewTarget
。
@Override
public void onResourceReady(Z resource, GlideAnimation<? super Z> glideAnimation) {
if (glideAnimation == null || !glideAnimation.animate(resource, this)) {
setResource(resource);
}
}
onResourceReady()
方法通过对glideAnimation
进行空值判断和对glideAnimation.animate()
返回值进行分析,来决定是否执行setResource()
方法。ImageViewTarget
的setResource()
方法是一个抽象方法,具体执行逻辑由GlideDrawableImageViewTarget
实现。
protected void setResource(GlideDrawable resource) {
view.setImageDrawable(resource);
}
调用view.setImageDrawable()
方法,将GlideDrawable
显示到ImageView
上。这个view
就是在调用ImageViewTargetFactory
的buildTarget()
方法,对GlideDrawableImageViewTarget
进行实例化时传入的ImageView
。
setResource()
方法的调用条件是glideAnimation
为空或glideAnimation.animate()
方法返回 false
。
根据animationFactory
引用工厂对象的不同,onResourceReady()
方法可能传入DrawableCrossFadeViewAnimation
或NoAnimation
对象,这就是glideAnimation
所对应对象实例。
DrawableCrossFadeViewAnimation
和NoAnimation
两者也人如其名,差异主要体现在animate()
方法之上。
@Override
public boolean animate(Object current, ViewAdapter adapter) {
return false;
}
NoAnimation
的animate()
方法直接返回false,未实现任何动画效果。恰好符合setResource()
方法调用条件,通过view.setImageDrawable()
显示图片。
@Override
public boolean animate(T current, ViewAdapter adapter) {
Drawable previous = adapter.getCurrentDrawable();
if (previous != null) {
TransitionDrawable transitionDrawable = new TransitionDrawable(new Drawable[] { previous, current });
transitionDrawable.setCrossFadeEnabled(true);
transitionDrawable.startTransition(duration);
adapter.setDrawable(transitionDrawable);
return true;
} else {
defaultAnimation.animate(current, adapter);
return false;
}
}
DrawableCrossFadeViewAnimation.animate()
方法内部先是获取GlideDrawableImageViewTarget
的占位符previous
。如果previous
不为空,即先前通过placeHolder()
设置占位符,则通过TransitionDrawable
设置动画并添加图片至ImageView
。否则通过defaultAnimation
展示图片。
这里的defaultAnimation
是一个ViewAnimation
或NoAnimation
对象。由ViewAnimationFactory
的build()
方法创建并返回。不知道快去回到👆看看getFirstResourceAnimation()
方法的分析。
public boolean animate(R current, ViewAdapter adapter) {
View view = adapter.getView();
if (view != null) {
view.clearAnimation();
Animation animation = animationFactory.build();
view.startAnimation(animation);
}
return false;
}
ViewAnimation.animate()
方法内部通过view.startAnimation(animation)
实现动画效果,并返回false
。这里的animationFactory
是DrawableCrossFadeFactory
的静态内部类DefaultAnimationFactory
。 animationFactory.build()
方法内部仅创建并返回一个AlphaAnimation
对象实例。并调用view.startAnimation()
执行AlphaAnimation
对应的淡入淡出动画。
NoAnimation
则只是简单的返回false
。在previous
为空的情况下,无论调用ViewAnimation
还是NoAnimation
的animate()
方法,图片加载都将交给view.setImageDrawable()
执行。
分析至此,结合问题及Glide源码,占位符、默认动画、缓存机制的共性在于都对TransitionDrawable.animate()
方法在不同方面产生影响。一张图片胜过千言万语,快来通过👇这张图来梳理下Glide
相关问题得以解决的原因。