We use the following code in our normal project
GlideApp
.with(context)
.load(url)
.into(imageView);
Copy the code
How does Glide load the image correctly in our quick slide, when we use the code above in a common list interface (e.g. recycleView list), without causing the image content to be misused or incorrect?
To achieve this, glide, in a nutshell, has to execute the code above and load the latest image onto the correct object, while cancelling the image load request associated with the object.
Let’s start with the into() method.
/**
* Sets the {@link ImageView} the resource will be loaded into, cancels any existing loads into
* the view, and frees any resources Glide may have previously loaded into the view so they may * be reused.
*
* @see RequestManager#clear(Target)
*
* @param view The view to cancel previous loads for and load the new resource into.
* @return The
* {@link com.bumptech.glide.request.target.Target} used to wrap the given {@link 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) {
// Clone in this method so that if we use this RequestBuilder to load into a View and then
// into a different target, we don't retain the transformation applied based on the previous
// View's scale type.
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);
}
Copy the code
In fact, the method comments already prove what I said above. The comments are as follows:
Set the resources to be loaded to the ImagView, cancel any existing loading related to ImageView X, and release resources that Glide might have loaded to the View before so that they can be reused.
You can see that 45 lines of code is the key. Look at the code.
private <Y extends Target<TranscodeType>> Y into( @NonNull Y target, @Nullable RequestListener
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;
}
Copy the code
Glide code is still quite complex, here I will not be a line by line analysis of the specific implementation, you can be interested in the place to explore their own, here we mainly look at the main process mentioned above.
You can see 15 lines of code building a Request and holding a Target. This notifies target of the result.
Request request = buildRequest(target, targetListener, options);
Copy the code
The above input parameter target is specified by
glideContext.buildImageViewTarget(view, transcodeClass)
Copy the code
We just need to know that Target is a wrap and abstraction of the Target that we want to load.
I’ll post the interface definition and implementation to help you understand a little bit. There are many kinds of implementations, and here are the easiest internal implementations to use in common usage.
/** * An interface that Glide can load a resource into and notify of relevant lifecycle events during a * load. * * <p> The lifecycle events in this class are as follows: <ul> <li>onLoadStarted</li> * <li>onResourceReady</li> <li>onLoadCleared</li> <li>onLoadFailed</li> </ul> * * The typical lifecycle is onLoadStarted -> onResourceReady or onLoadFailed -> onLoadCleared. * However, there are no guarantees. onLoadStarted may not be called if the resource is in memory or * if the load will fail because of a null model object. onLoadCleared similarly may never be called * if the target is never cleared. See the docs for the individual methods for details. </p> * *@param <R> The type of resource the target can display.
*/
public interface Target<R> extends LifecycleListener {
/** * Indicates that we want the resource in its original unmodified width and/or height. */
int SIZE_ORIGINAL = Integer.MIN_VALUE;
/**
* A lifecycle callback that is called when a load is started.
*
* <p> Note - This may not be called for every load, it is possible for example for loads to fail
* before the load starts (when the model object is null).
*
* <p> Note - This method may be called multiple times before any other lifecycle method is
* called. Loads can be paused and restarted due to lifecycle or connectivity events and each
* restart may cause a call here.
*
* <p>You must ensure that any current Drawable received in {@link #onResourceReady(Object,
* Transition)} is no longer displayed before redrawing the container (usually a View) or
* changing its visibility.
*
* @param placeholder The placeholder drawable to optionally show, or null.
*/
void onLoadStarted(@Nullable Drawable placeholder);
/**
* A lifecycle callback that is called when a load fails.
*
* <p> Note - This may be called before {@link #onLoadStarted(android.graphics.drawable.Drawable)
* } if the model object is null.
*
* <p>You must ensure that any current Drawable received in {@link #onResourceReady(Object,
* Transition)} is no longer displayed before redrawing the container (usually a View) or
* changing its visibility.
*
* @param errorDrawable The error drawable to optionally show, or null.
*/
void onLoadFailed(@Nullable Drawable errorDrawable);
/**
* The method that will be called when the resource load has finished.
*
* @param resource the loaded resource.
*/
void onResourceReady(@NonNull R resource, @Nullable Transition<? super R> transition);
/**
* A lifecycle callback that is called when a load is cancelled and its resources are freed.
*
* <p>You must ensure that any current Drawable received in {@link #onResourceReady(Object,
* Transition)} is no longer displayed before redrawing the container (usually a View) or
* changing its visibility.
*
* @param placeholder The placeholder drawable to optionally show, or null.
*/
void onLoadCleared(@Nullable Drawable placeholder);
/**
* A method to retrieve the size of this target.
*
* @param cb The callback that must be called when the size of the target has been determined
*/
void getSize(@NonNull SizeReadyCallback cb);
/**
* Removes the given callback from the pending set if it's still retained.
*
* @param cb The callback to remove.
*/
void removeCallback(@NonNull SizeReadyCallback cb);
/** * Sets the current request for this target to retain, should not be called outside of Glide. */
void setRequest(@Nullable Request request);
/** * Retrieves the current request for this target, should not be called outside of Glide. */
@Nullable
Request getRequest(a);
}
Copy the code
/**
* A base {@link com.bumptech.glide.request.target.Target} for displaying resources in {@link
* android.widget.ImageView}s.
*
* @param <Z> The type of resource that this target will display in the wrapped {@link
* android.widget.ImageView}.
*/
// Public API.
@SuppressWarnings("WeakerAccess")
public abstract class ImageViewTarget<Z> extends ViewTarget<ImageView.Z>
implements Transition.ViewAdapter {
@Nullable
private Animatable animatable;
public ImageViewTarget(ImageView view) {
super(view);
}
/ * * *@deprecated Use {@link #waitForLayout()} instead.
*/
@SuppressWarnings({"deprecation"})
@Deprecated
public ImageViewTarget(ImageView view, boolean waitForLayout) {
super(view, waitForLayout);
}
/**
* Returns the current {@link android.graphics.drawable.Drawable} being displayed in the view
* using {@link android.widget.ImageView#getDrawable()}.
*/
@Override
@Nullable
public Drawable getCurrentDrawable(a) {
return view.getDrawable();
}
/**
* Sets the given {@link android.graphics.drawable.Drawable} on the view using {@link
* android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)}.
*
* @param drawable {@inheritDoc} * /
@Override
public void setDrawable(Drawable drawable) {
view.setImageDrawable(drawable);
}
/**
* Sets the given {@link android.graphics.drawable.Drawable} on the view using {@link
* android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)}.
*
* @param placeholder {@inheritDoc} * /
@Override
public void onLoadStarted(@Nullable Drawable placeholder) {
super.onLoadStarted(placeholder);
setResourceInternal(null);
setDrawable(placeholder);
}
/**
* Sets the given {@link android.graphics.drawable.Drawable} on the view using {@link
* android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)}.
*
* @param errorDrawable {@inheritDoc} * /
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {
super.onLoadFailed(errorDrawable);
setResourceInternal(null);
setDrawable(errorDrawable);
}
/**
* Sets the given {@link android.graphics.drawable.Drawable} on the view using {@link
* android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)}.
*
* @param placeholder {@inheritDoc} * /
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
super.onLoadCleared(placeholder);
if(animatable ! =null) {
animatable.stop();
}
setResourceInternal(null);
setDrawable(placeholder);
}
@Override
public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
if (transition == null| |! transition.transition(resource,this)) {
setResourceInternal(resource);
} else{ maybeUpdateAnimatable(resource); }}@Override
public void onStart(a) {
if(animatable ! =null) { animatable.start(); }}@Override
public void onStop(a) {
if(animatable ! =null) { animatable.stop(); }}private void setResourceInternal(@Nullable Z resource) {
// Order matters here. Set the resource first to make sure that the Drawable has a valid and
// non-null Callback before starting it.
setResource(resource);
maybeUpdateAnimatable(resource);
}
private void maybeUpdateAnimatable(@Nullable Z resource) {
if (resource instanceof Animatable) {
animatable = (Animatable) resource;
animatable.start();
} else {
animatable = null; }}protected abstract void setResource(@Nullable Z resource);
}
Copy the code
in
into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
@NonNull RequestOptions options)
Copy the code
In lines 15 to 19 of the method, determine whether the previous Request in the Taget is the same as the newly constructed Request, and if they are the same, reclaim the latest Request and let the old Request continue. If not, disassociate the previous Request and target.
The logical code is 31 lines
requestManager.clear(target);
Copy the code
This will eventually trigger the following code
private void untrackOrDelegate(@NonNull Target
target) {
boolean isOwnedByUs = untrack(target);
// We'll end up here if the Target was cleared after the RequestManager that started the request
// is destroyed. That can happen for at least two reasons:
// 1. We call clear() on a background thread using something other than Application Context
// RequestManager.
// 2. The caller retains a reference to the RequestManager after the corresponding Activity or
// Fragment is destroyed, starts a load with it, and then clears that load with a different
// RequestManager. Callers seem especially likely to do this in retained Fragments (#2262).
//
// #1 is always an error. At best the caller is leaking memory briefly in something like an
// AsyncTask. At worst the caller is leaking an Activity or Fragment for a sustained period of
// time if they do something like reference the Activity RequestManager in a long lived
// background thread or task.
//
// #2 is always an error. Callers shouldn't be starting new loads using RequestManagers after
// the corresponding Activity or Fragment is destroyed because retaining any reference to the
// RequestManager leaks memory. It's possible that there's some brief period of time during or
// immediately after onDestroy where this is reasonable, but I can't think of why.
if(! isOwnedByUs && ! glide.removeFromManagers(target) && target.getRequest() ! =null) {
Request request = target.getRequest();
target.setRequest(null); request.clear(); }}Copy the code
As you can see, the request corresponding to target is null, while the old request is “clear”. How does the resource not load to the associated Target after the old Request is cleared? Let’s look at the implementation of SingleRequest
/**
* Cancels the current load if it is in progress, clears any resources held onto by the request
* and replaces the loaded resource if the load completed with the placeholder.
*
* <p> Cleared requests can be restarted with a subsequent call to {@link #begin()} </p>
*
* @see #cancel()
*/
@Override
public void clear(a) {
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());
}
// Must be after cancel().
status = Status.CLEARED;
}
Copy the code
You can see that cancel() is performed first in the clear() method, which disassociates the load resource Request with the Request callback.
/**
* Cancels the current load but does not release any resources held by the request and continues
* to display the loaded resource if the load completed before the call to cancel.
*
* <p> Cancelled requests can be restarted with a subsequent call to {@link #begin()}. </p>
*
* @see #clear()
*/
void cancel(a) {
assertNotCallingCallbacks();
stateVerifier.throwIfRecycled();
target.removeCallback(this);
status = Status.CANCELLED;
if(loadStatus ! =null) {
loadStatus.cancel();
loadStatus = null; }}Copy the code
LoadStatus actually just holds the callback and EngineJob.
/** * A callback that listens for when a resource load completes successfully or fails due to an * exception. */
public interface ResourceCallback {
/**
* Called when a resource is successfully loaded.
*
* @param resource The loaded resource.
*/
void onResourceReady(Resource
resource, DataSource dataSource);
/**
* Called when a resource fails to load successfully.
*
* @param e a non-null {@link GlideException}.
*/
void onLoadFailed(GlideException e);
}
Copy the code
/** * Allows a request to indicate it no longer is interested in a given load. */
public static class LoadStatus {
private finalEngineJob<? > engineJob;private finalResourceCallback cb; LoadStatus(ResourceCallback cb, EngineJob<? > engineJob) {this.cb = cb;
this.engineJob = engineJob;
}
public void cancel(a) { engineJob.removeCallback(cb); }}Copy the code
The EngineJob is responsible for loading the resource and calling back when it is successfully loaded. In this case, SingleRequest implements the callback, so it knows that the resource has been loaded and retrieved. The EngineJob implementation is not analyzed here to avoid straying too far from the main flow.
So after the cancel() call, there is no callback to Tareget even if the old load request completes.
in
target.setRequest(request);
requestManager.track(target, request);
Copy the code
The Target () method holds the latest request, and the requestManager.track() method triggers the request load. The internal Engine and Engine job are responsible for the load. When successfully loaded, it is called back to the Target object, firing the target.onResourceReady(result, animation) method, and the image is displayed correctly.
In fact, there are a lot of details. Here is just a general introduction of the main process, I hope to help you.
The above code is based on Glide V4.7.1