我们都知道,Glide是在Picasso的基础上进行的改善,相比于Picasso,Glide会根据ImageView的大小来生成图片大小,这样可以减少图片占用的内存大小,我们来看看Glide是怎么动态测量获取ImageView的大小,并且设置根据大小设置图片的大小
我们先来简单回顾一下Activity中加载视图的过程:
我们在onCreate(...)
方法中调用了setContentView(int layoutId)
来设置了布局文件,其实这里并没有马上进行视图的测量,布局,绘制,一切都是等到onResume之后才进行界面视图的渲染。至于如何渲染,这里有两篇文章,如果你不熟悉或者还是小白,可以先参考下:
Android View的绘制流程
Android应用程序启动过程源代码分析
那么在这个生命周期过程中,如果使用了Glide的加载图片方法,那么其实这时候图片是无法确定大小和位置的(因为这时候整个页面视图还没有进行测量布局绘制,哪来的确定大小和位置),这时候Glide该怎么办呢?
还是以我们上文Glide源码分析(一) 图片加载的生命周期中的例子来分析一下整个过程,重点讲解一下动态获取ImageView大小
例子
Glide.with(StartActivity.this).load(R.mipmap.pizza).into(mIvShow);
分析
本例中,Glide前面的方法最终要生成Bitmap对象写入ImageView对象中显示出来,我们看下into()
方法
GenericRequestBuilder.java
// 设置了将要加载图片到哪个视图,取消已经加载到ImageView中的资源,并且释放资源用于后面可能的复用
public Target<TranscodeType> into(ImageView view) {
Util.assertMainThread();
if (view == null) {
throw new IllegalArgumentException("You must pass in a non null View");
}
// 如果之前没有定义过Transformation,并且ImageView设置了scaleType
if (!isTransformationSet && view.getScaleType() != null) {
switch (view.getScaleType()) {
case CENTER_CROP:
// 生成一个CENTER_CROP的Transformation,用于后面生成图片时转换
applyCenterCrop();
break;
case FIT_CENTER:
case FIT_START:
case FIT_END:
// 生成一个FIT_END的Transformation对象,用于后面生成图片时转换
applyFitCenter();
break;
//$CASES-OMITTED$
default:
// Do nothing.
}
}
// 统一构建ImageViewTarget
return into(glide.buildImageViewTarget(view, transcodeClass));
}
这里最终会构建一个ImageViewTarget对象,接下来看下是如何构建这个类
关于自定义Transformation的例子,具体看我的例子工程GlideSample的TransformationActivity.java
Glide.java
<R> Target<R> buildImageViewTarget(ImageView imageView, Class<R> transcodedClass) {
return imageViewTargetFactory.buildTarget(imageView, transcodedClass);
}
ImageViewTargetFactory.java
@SuppressWarnings("unchecked")
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)) {
......
}
......
}
这里我们最终会返回GlideDrawableImageViewTarget
对象,接下来就是request任务
GenericRequestBuilder.java
public <Y extends Target<TranscodeType>> Y into(Y target) {
Util.assertMainThread();
if (target == null) {
throw new IllegalArgumentException("You must pass in a non null Target");
}
if (!isModelSet) {
throw new IllegalArgumentException("You must first set a model (try #load())");
}
// 这个target之前是否有存在Request任务
Request previous = target.getRequest();
if (previous != null) {
// 如果存在,那么清除之前的Request
previous.clear();
requestTracker.removeRequest(previous);
previous.recycle();
}
// 构建新的Request,下面会讲解到
Request request = buildRequest(target);
target.setRequest(request);
lifecycle.addListener(target);
// 运行Request,本文重点在这里
requestTracker.runRequest(request);
return target;
}
上面代码我们主要关注下buildRequest(target)
方法和requestTracker.runRequest(request)
方法(本文重点)
GenericRequestBuilder.java
private Request buildRequest(Target<TranscodeType> target) {
if (priority == null) {
priority = Priority.NORMAL;
}
return buildRequestRecursive(target, null);
}
private Request buildRequestRecursive(Target<TranscodeType> target, ThumbnailRequestCoordinator parentCoordinator) {
if (thumbnailRequestBuilder != null) { // 是否有指定自定义缩略图的请求,本例中没有
......
} else if (thumbSizeMultiplier != null) { // 是否有等比例缩放的请求,本例中没有
......
} else {
// 没有缩略图,构建一个GenericRequest对象
return obtainRequest(target, sizeMultiplier, priority, parentCoordinator);
}
}
关于缩略图,具体看我的例子工程GlideSample的ThumbnailActivity.java
类,里面有关于缩略图的用法
好了,前面铺垫了这么多,其实还没有涉及到如何获取ImageView的大小,别急,马上就来~~
我们看下requestTracker.runRequest(target)
方法
RequestTracker.java
public void runRequest(Request request) {
requests.add(request);
if (!isPaused) {
request.begin();
} else {
pendingRequests.add(request);
}
}
这个方法运行了封装好的GenericRequest.begin()
类方法
GenericRequest.java
public void begin() {
// 记录Request运行起始时间
startTime = LogTime.getLogTime();
if (model == null) {
onException(null);
return;
}
// 设置为等待测量ImageView大小
status = Status.WAITING_FOR_SIZE;
// overrideWidth和overrideHeight是用户可能通过Glide.override(x,y)
// 在显示图片前重新剪裁图片大小
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
用户已经指定图片大小,无需测量ImageView大小
onSizeReady(overrideWidth, overrideHeight);
} else {
// 这里的target是我们上文中得到的GlideDrawableImageViewTarget,
// 通过target类设置了一个监听进去,来监听ImageView的图片固定
target.getSize(this);
}
if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {
// 开始加载占位符图片(如果用户有设置占位符的情况下)
target.onLoadStarted(getPlaceholderDrawable());
}
// 记录本次run的时间
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished run method in " + LogTime.getElapsedMillis(startTime));
}
}
重点在这里target.getSize(this)
,这个类设置了
GlideDrawableImageViewTarget.java
public void getSize(SizeReadyCallback cb) {
sizeDeterminer.getSize(cb);
}
GlideDrawableImageViewTarget.java
public void getSize(SizeReadyCallback cb) {
// 获取ImageView的准确宽度或者LayoutParams的常量值(例如:LayoutParams.WRAP_CONTENT)
int currentWidth = getViewWidthOrParam();
// 同上面获取宽度
int currentHeight = getViewHeightOrParam();
// 如果宽度是准确值,或者是LayoutParams.WRAP_CONTENT属性
// 如果高度是准确值,或者是LayoutParams.WRAP_CONTENT属性
if (isSizeValid(currentWidth) && isSizeValid(currentHeight)) {
// 直接回调已获取到当前的宽高
cb.onSizeReady(currentWidth, currentHeight);
} else {
// 加入到回调队列中
if (!cbs.contains(cb)) {
cbs.add(cb);
}
if (layoutListener == null) {
// 获取ImageView的视图树监听
final ViewTreeObserver observer = view.getViewTreeObserver();
layoutListener = new SizeDeterminerLayoutListener(this);
/*
重点来咯,添加一个OnPreDrawListener这个监听,关于这个监听的含义大体
是在视图绘制之前进行回调,绘制的时候,视图的肯定是经过测量过宽高了,别问我为什么,我会打人的
*/
observer.addOnPreDrawListener(layoutListener);
}
}
}
我们看下SizeDeterminerLayoutListener
这个类,实现了ViewTreeObserver.OnPreDrawListener
接口,通过onPreDraw方法,执行回调队列进行统一的回调
private static class SizeDeterminerLayoutListener implements ViewTreeObserver.OnPreDrawListener {
/* 这里为什么用弱引用,我想是因为我们的ViewTarget可能在测量ImageView前
有可能进行多次的Glide.into(),还记得我们在GenericRequestBuilder.into()方法吗?
里面有对之前的Request进行clear,recycle等操作,忘记的同学可以回头去看看代码
*/
private final WeakReference<SizeDeterminer> sizeDeterminerRef;
public SizeDeterminerLayoutListener(SizeDeterminer sizeDeterminer) {
sizeDeterminerRef = new WeakReference<SizeDeterminer>(sizeDeterminer);
}
@Override
public boolean onPreDraw() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "OnGlobalLayoutListener called listener=" + this);
}
SizeDeterminer sizeDeterminer = sizeDeterminerRef.get();
if (sizeDeterminer != null) {
// 统一回调,可以获取当前ImageView的宽高
sizeDeterminer.checkCurrentDimens();
}
return true;
}
}
SizeDeterminer.java
private void checkCurrentDimens() {
if (cbs.isEmpty()) {
return;
}
// 上面已经解释过了
int currentWidth = getViewWidthOrParam();
int currentHeight = getViewHeightOrParam();
if (!isSizeValid(currentWidth) || !isSizeValid(currentHeight)) {
return;
}
// 统一回调监听队列
notifyCbs(currentWidth, currentHeight);
// Keep a reference to the layout listener and remove it here
// rather than having the observer remove itself because the observer
// we add the listener to will be almost immediately merged into
// another observer and will therefore never be alive. If we instead
// keep a reference to the listener and remove it here, we get the
// current view tree observer and should succeed.
ViewTreeObserver observer = view.getViewTreeObserver();
if (observer.isAlive()) {
// 测量得到了结果,移除监听
observer.removeOnPreDrawListener(layoutListener);
}
layoutListener = null;
}
回调方法notifyCbs(currentWidth, currentHeight)
最后回调GenericRequest.onSizeReady()方法对图片进行加载显示,本节暂时不对图片的加载源码进行分析
GenericRequest.java
public void onSizeReady(int width, int height) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
}
if (status != Status.WAITING_FOR_SIZE) {
return;
}
status = Status.RUNNING;
// 图片缩放参数,如果用户有设置thumbnail参数时`Glide.with(...).thumbnail(0.2f).into(...)`进行比例缩放
// 默认值是1,不进行图片大小缩放
width = Math.round(sizeMultiplier * width);
height = Math.round(sizeMultiplier * height);
// 图片加载
......
......
......
}
总结
通过本节的源码分析,我们可以发现Glide将加载图片到ImageVIew封装成Request对象,在run Request的时候进行判断,如果ImageView有固定大小,那么就直接回调加载图片,如果图片还没有进行测量过,那么就设置监听ViewTreeObserver.OnPreDrawListener,等待ImageView绘制前回调监听获取视图大小,在加载确定大小的图片