Glide可以智能的根據(jù)View的大小來合適的設(shè)置圖片需要顯示的大小,這樣可以有效的減小內(nèi)存使用。那么要優(yōu)化需要顯示的圖片大小,前提條件肯定是知道target(view)的大小,這樣才能進(jìn)行合適的裁剪。今天這篇文章主要來分析下Glide怎么動(dòng)態(tài)測(cè)量view的大小(本文分析源碼來自Glide-4.8.0版本)。
一般使用Glide方式如下:
Glide.with(this).load(URL).into(imageview)
如果要獲取view的大小可以給Target設(shè)置一個(gè)回調(diào),Glide會(huì)把計(jì)算得到的width和height返回:
Glide.with(this).load(URL).into(imageview).getSize(new SizeReadyCallback() {
@Override
public void onSizeReady(int width, int height) {
Log.i(TAG, "width = " + width + ", height = " + height);
}
});
首先跟到into中看下源碼,會(huì)調(diào)用buildImageViewTarget來構(gòu)造Target:
// RequestBuilder.java
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);
}
// GlideContext
@NonNull
public <X> ViewTarget<ImageView, X> buildImageViewTarget(
@NonNull ImageView imageView, @NonNull Class<X> transcodeClass) {
return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
}
接著會(huì)走到ImageViewTargetFactory中,在我們這里就返回DrawableImageViewTarget, 如果在構(gòu)造RequestBuilder過程中調(diào)用操作asBitmap,那么這里就會(huì)返回BitmapImageViewTarget:
// ImageViewTargetFactory
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)");
}
}
}
再返回到上面的into(ImageView)方法中,最后會(huì)調(diào)用內(nèi)部私有方法into(Target, RequestListener, RequestOptions), 該方法首先通過buildRequest構(gòu)造一個(gè)Request,我們這里沒有設(shè)置thumbnail,默認(rèn)會(huì)返回SingleRequest,
接下來如果這個(gè)target中有request在進(jìn)行會(huì)先clear掉,然后再加載本次的Request:
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();
previous.begin();
}
return target;
}
requestManager.clear(target);
target.setRequest(request);
requestManager.track(target, request);
return target;
}
其中主要的邏輯是下面的兩行代碼:
requestManager.clear(target);
requestManager.track(target, request);
在clear中會(huì)釋放Resource,然后回調(diào)onLoadCleared:
public void clear() {
Util.assertMainThread();
assertNotCallingCallbacks();
stateVerifier.throwIfRecycled();
if (status == Status.CLEARED) {
return;
}
cancel();
// Resource must be released before canNotifyStatusChanged is called.
if (resource != null) {
releaseResource(resource);
}
if (canNotifyCleared()) {
target.onLoadCleared(getPlaceholderDrawable());
}
status = Status.CLEARED;
}
接著看下面的requestManager.track(target, request):
第一行代碼很簡(jiǎn)單,就是把這次的target加入到Glide的Targets管理集合中
// RequestManager.java
void track(@NonNull Target<?> target, @NonNull Request request) {
targetTracker.track(target);
requestTracker.runRequest(request);
}
真正的邏輯在runRequest中,調(diào)用Request的begin開始工作。
// RequestTracker.java
/**
* Starts tracking the given request.
*/
public void runRequest(@NonNull Request request) {
requests.add(request);
if (!isPaused) {
request.begin();
} else {
request.clear();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Paused, delaying request");
}
pendingRequests.add(request);
}
}
那么饒了一大圈還只是做一些準(zhǔn)備工作,還沒開始真正開始測(cè)量,前面說到這里的request是SingleRequest,接著往下看:
// SingleRequest.java
public void begin() {
...
if (status == Status.COMPLETE) {
onResourceReady(resource, DataSource.MEMORY_CACHE);
return;
}
// Restarts for requests that are neither complete nor running can be treated as new requests
// and can run again from the beginning.
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());
}
if (IS_VERBOSE_LOGGABLE) {
logV("finished run method in " + LogTime.getElapsedMillis(startTime));
}
}
首先,如果我們?cè)贕lide加載的時(shí)候通過override給了固定的寬高,那么這里就會(huì)立馬回調(diào)onSizeReady函數(shù),這個(gè)函數(shù)代碼我們后面一起看,這里先接著往下看。在我們這里會(huì)走到else邏輯中,調(diào)用getSize函數(shù),這個(gè)主流程后面再分析,先把begin函數(shù)看完。如果沒有給定寬高,那么往下走就會(huì)回調(diào)target的onLoadStarted接口,在這里可以顯示占位符。到這里into的邏輯就走完了,那加載工作在哪里開始?沒錯(cuò),就是在上面留下的target.getSize函數(shù)中。
前面知道這里的Target是DrawableImageViewTarget,該方法在它的父類ViewTarget中,
- 該方法首先調(diào)用
View.getWidth()/View.getHeight(),如果其中一個(gè)或者兩個(gè)為0, - 那么接著檢查
View's LayoutParams, - 如果有其中一個(gè)或者兩個(gè)<=0,
- 那么就會(huì)添加一個(gè)
OnPreDrawListener接口,在回調(diào)接口SizeDeterminerLayoutListener中就再調(diào)用checkCurrentDimens方法重復(fù)一遍獲取view的寬高,如果有效就回調(diào)SizeReadyCallback:
// DrawableImageViewTarget.java
@CallSuper
@Override
public void getSize(@NonNull SizeReadyCallback cb) {
sizeDeterminer.getSize(cb);
}
static final class SizeDeterminer {
void getSize(@NonNull SizeReadyCallback cb) {
int currentWidth = getTargetWidth();
int currentHeight = getTargetHeight();
if (isViewStateAndSizeValid(currentWidth, currentHeight)) {
cb.onSizeReady(currentWidth, currentHeight);
return;
}
if (!cbs.contains(cb)) {
cbs.add(cb);
}
if (layoutListener == null) {
ViewTreeObserver observer = view.getViewTreeObserver();
layoutListener = new SizeDeterminerLayoutListener(this);
observer.addOnPreDrawListener(layoutListener);
}
}
void checkCurrentDimens() {
if (cbs.isEmpty()) {
return;
}
int currentWidth = getTargetWidth();
int currentHeight = getTargetHeight();
if (!isViewStateAndSizeValid(currentWidth, currentHeight)) {
return;
}
notifyCbs(currentWidth, currentHeight);
clearCallbacksAndListener();
}
}
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;
}
}
在SingleRequest中知道這里SizeReadyCallback就是SingleRequest:
// SingleRequest.java
@Override
public void onSizeReady(int width, int height) {
stateVerifier.throwIfRecycled();
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);
// This is a hack that's only useful for testing right now where loads complete synchronously
// even though under any executor running on any thread but the main thread, the load would
// have completed asynchronously.
if (status != Status.RUNNING) {
loadStatus = null;
}
if (IS_VERBOSE_LOGGABLE) {
logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
}
}
到這里就調(diào)用Engine.load方法開始漫長(zhǎng)的加載流程,這里可以參考我之前的Glide緩存流程.
所以Glide在真正加載之前會(huì)先去確定View的尺寸,如果沒有通過override方法設(shè)置view的固定尺寸,那么會(huì)分別通過getWidth()/getHeight()和View's LayoutParams方法獲取尺寸,如果這兩個(gè)方法還不能獲取有效尺寸,就會(huì)通過OnPreDrawListener`添加回調(diào)接口來獲取尺寸。