The last article talked about the basic functions and uses of Glide4.x. If you don't know Glide4, you can check out the previous article. Glide 4 Parsing Series (1): How to Use Glide Today's article is an analysis of Glide4 from the source point of view.
The core of Glide's basic usage in the last article is just one sentence: Glide. with (this). load (mUrl). in (mImageView); the code is simple, so let's go further from that sentence.
I. with
First, let's look at what's done in the Glide.with () source code. Click on with and we will first go into the Glide class and see the source code:
/** * Begin a load with Glide by passing in a context. * * <p> Any requests started using a context will only have the application level options applied * and will not be started or stopped based on lifecycle events. In general, loads should be * started at the level the result will be used in. If the resource will be used in a view in a * child fragment, the load should be started with {@link #with(android.app.Fragment)}} using that * child fragment. Similarly, if the resource will be used in a view in the parent fragment, the * load should be started with {@link #with(android.app.Fragment)} using the parent fragment. In * the same vein, if the resource will be used in a view in an activity, the load should be * started with {@link #with(android.app.Activity)}}. </p> * * <p> This method is appropriate for resources that will be used outside of the normal fragment * or activity lifecycle (For example in services, or for notification thumbnails). </p> * * @param context Any context, will not be retained. * @return A RequestManager for the top level application that can be used to start a load. * @see #with(android.app.Activity) * @see #with(android.app.Fragment) * @see #with(android.support.v4.app.Fragment) * @see #with(android.support.v4.app.FragmentActivity) */ @NonNull public static RequestManager with(@NonNull Context context) { return getRetriever(context).get(context); } /** * Begin a load with Glide that will be tied to the given {@link android.app.Activity}'s lifecycle * and that uses the given {@link Activity}'s default options. * * @param activity The activity to use. * @return A RequestManager for the given activity that can be used to start a load. */ @NonNull public static RequestManager with(@NonNull Activity activity) { return getRetriever(activity).get(activity); } /** * Begin a load with Glide that will tied to the give * {@link android.support.v4.app.FragmentActivity}'s lifecycle and that uses the given * {@link android.support.v4.app.FragmentActivity}'s default options. * * @param activity The activity to use. * @return A RequestManager for the given FragmentActivity that can be used to start a load. */ @NonNull public static RequestManager with(@NonNull FragmentActivity activity) { return getRetriever(activity).get(activity); } /** * Begin a load with Glide that will be tied to the given * {@link android.support.v4.app.Fragment}'s lifecycle and that uses the given * {@link android.support.v4.app.Fragment}'s default options. * * @param fragment The fragment to use. * @return A RequestManager for the given Fragment that can be used to start a load. */ @NonNull public static RequestManager with(@NonNull Fragment fragment) { return getRetriever(fragment.getActivity()).get(fragment); } /** * Begin a load with Glide that will be tied to the given {@link android.app.Fragment}'s lifecycle * and that uses the given {@link android.app.Fragment}'s default options. * * @param fragment The fragment to use. * @return A RequestManager for the given Fragment that can be used to start a load. * @deprecated Prefer support Fragments and {@link #with(Fragment)} instead, * {@link android.app.Fragment} will be deprecated. See * https://github.com/android/android-ktx/pull/161#issuecomment-363270555. */ @SuppressWarnings("deprecation") @Deprecated @NonNull public static RequestManager with(@NonNull android.app.Fragment fragment) { return getRetriever(fragment.getActivity()).get(fragment); } /** * Begin a load with Glide that will be tied to the lifecycle of the {@link Fragment}, * {@link android.app.Fragment}, or {@link Activity} that contains the View. * * <p>A {@link Fragment} or {@link android.app.Fragment} is assumed to contain a View if the View * is a child of the View returned by the {@link Fragment#getView()}} method. * * <p>This method will not work if the View is not attached. Prefer the Activity and Fragment * variants unless you're loading in a View subclass. * * <p>This method may be inefficient aways and is definitely inefficient for large hierarchies. * Consider memoizing the result after the View is attached or again, prefer the Activity and * Fragment variants whenever possible. * * <p>When used in Applications that use the non-support {@link android.app.Fragment} classes, * calling this method will produce noisy logs from {@link android.app.FragmentManager}. Consider * avoiding entirely or using the {@link Fragment}s from the support library instead. * * <p>If the support {@link FragmentActivity} class is used, this method will only attempt to * discover support {@link Fragment}s. Any non-support {@link android.app.Fragment}s attached * to the {@link FragmentActivity} will be ignored. * * @param view The view to search for a containing Fragment or Activity from. * @return A RequestManager that can be used to start a load. */ @NonNull public static RequestManager with(@NonNull View view) { return getRetriever(view.getContext()).get(view); }
As you can see in the code, there are many with methods with different parameters, namely Context, Acyivity, Fragment s, etc., which will not be discussed here. Although the parameters are different, the purpose of the with method is the same, both for returning a RequestManager object. So what does Request Manager do?
* @return A RequestManager that can be used to start a load.
As we can see from the annotations, the RequestManager object is used to prepare for the next load to start loading data. So how does the RequestManager object get it? In the with method, the most important sentence is getRetriever(activity).get(activity); we can see the following code when we follow the getRetriever method:
@NonNull private static RequestManagerRetriever getRetriever(@Nullable Context context) { // Context could be null for other reasons (ie the user passes in null), but in practice it will // only occur due to errors with the Fragment lifecycle. Preconditions.checkNotNull( context, "You cannot start a load on a not yet attached View or a Fragment where getActivity() " + "returns null (which usually occurs when getActivity() is called before the Fragment " + "is attached or after the Fragment is destroyed)."); return Glide.get(context).getRequestManagerRetriever(); }
See in the getRetriever method through Glide.get(context).getRequestManagerRetriever(); get the RequestManagerRetriever object, and then get the RequestManager through the RequestManagerRetriever.
Get RequestManager smoothly, then the work of with is finished. From the above code, you can see that the source code of the with method is still very simple, just to get RequestManager and start loading data.
Next, let's look at what's done in load ().
Two, load
Similarly, by clicking load, we enter the RequestManager class:
/** * Equivalent to calling {@link #asDrawable()} and then {@link RequestBuilder#load(Bitmap)}. * * @return A new request builder for loading a {@link Drawable} using the given model. */ @NonNull @CheckResult @Override public RequestBuilder<Drawable> load(@Nullable Bitmap bitmap) { return asDrawable().load(bitmap); } /** * Equivalent to calling {@link #asDrawable()} and then {@link RequestBuilder#load(Drawable)}. * * @return A new request builder for loading a {@link Drawable} using the given model. */ @NonNull @CheckResult @Override public RequestBuilder<Drawable> load(@Nullable Drawable drawable) { return asDrawable().load(drawable); } /** * Equivalent to calling {@link #asDrawable()} and then {@link RequestBuilder#load(String)}. * * @return A new request builder for loading a {@link Drawable} using the given model. */ @NonNull @CheckResult @Override public RequestBuilder<Drawable> load(@Nullable String string) { return asDrawable().load(string); } /** * Equivalent to calling {@link #asDrawable()} and then {@link RequestBuilder#load(Uri)}. * * @return A new request builder for loading a {@link Drawable} using the given model. */ @NonNull @CheckResult @Override public RequestBuilder<Drawable> load(@Nullable Uri uri) { return asDrawable().load(uri); } /** * Equivalent to calling {@link #asDrawable()} and then {@link RequestBuilder#load(File)}. * * @return A new request builder for loading a {@link Drawable} using the given model. */ @NonNull @CheckResult @Override public RequestBuilder<Drawable> load(@Nullable File file) { return asDrawable().load(file); } /** * Equivalent to calling {@link #asDrawable()} and then {@link RequestBuilder#load(Integer)}. * * @return A new request builder for loading a {@link Drawable} using the given model. */ @SuppressWarnings("deprecation") @NonNull @CheckResult @Override public RequestBuilder<Drawable> load(@RawRes @DrawableRes @Nullable Integer resourceId) { return asDrawable().load(resourceId); } /** * Equivalent to calling {@link #asDrawable()} and then {@link RequestBuilder#load(URL)}. * * @return A new request builder for loading a {@link Drawable} using the given model. */ @SuppressWarnings("deprecation") @CheckResult @Override @Deprecated public RequestBuilder<Drawable> load(@Nullable URL url) { return asDrawable().load(url); } /** * Equivalent to calling {@link #asDrawable()} and then {@link RequestBuilder#load(byte[])}. * * @return A new request builder for loading a {@link Drawable} using the given model. */ @NonNull @CheckResult @Override public RequestBuilder<Drawable> load(@Nullable byte[] model) { return asDrawable().load(model); } /** * A helper method equivalent to calling {@link #asDrawable()} and then {@link * RequestBuilder#load(Object)} with the given model. * * @return A new request builder for loading a {@link Drawable} using the given model. */ @NonNull @CheckResult @Override public RequestBuilder<Drawable> load(@Nullable Object model) { return asDrawable().load(model); }
As you can see from the above code, all load methods with different parameters have the same code asDrawable().load(model), in order to get the RequestBuilder object. RequestBuilder is used to process different types of resources and initial load. So how does Glide handle load? We then go into the asDrawable () method.
@NonNull @CheckResult public RequestBuilder<Drawable> asDrawable() { return as(Drawable.class); }
You can see that there is only one line of code in the asDrawable () method, and then we go into the as () method.
@NonNull @CheckResult public <ResourceType> RequestBuilder<ResourceType> as( @NonNull Class<ResourceType> resourceClass) { return new RequestBuilder<>(glide, this, resourceClass, context); }
As you can see from the above code, the as method doesn't do much processing, just a new RequestBuilder object. Then we go to load ():
@NonNull @CheckResult @Override public RequestBuilder<TranscodeType> load(@Nullable Bitmap bitmap) { return loadGeneric(bitmap) .apply(diskCacheStrategyOf(DiskCacheStrategy.NONE)); } @NonNull @CheckResult @Override public RequestBuilder<TranscodeType> load(@Nullable Drawable drawable) { return loadGeneric(drawable) .apply(diskCacheStrategyOf(DiskCacheStrategy.NONE)); } @NonNull @Override @CheckResult public RequestBuilder<TranscodeType> load(@Nullable String string) { return loadGeneric(string); } @NonNull @CheckResult @Override public RequestBuilder<TranscodeType> load(@Nullable Uri uri) { return loadGeneric(uri); } @NonNull @CheckResult @Override public RequestBuilder<TranscodeType> load(@Nullable File file) { return loadGeneric(file); } @NonNull @CheckResult @Override public RequestBuilder<TranscodeType> load(@RawRes @DrawableRes @Nullable Integer resourceId) { return loadGeneric(resourceId).apply(signatureOf(ApplicationVersionSignature.obtain(context))); } @Deprecated @CheckResult @Override public RequestBuilder<TranscodeType> load(@Nullable URL url) { return loadGeneric(url); } @NonNull @CheckResult @Override public RequestBuilder<TranscodeType> load(@Nullable byte[] model) { RequestBuilder<TranscodeType> result = loadGeneric(model); if (!result.requestOptions.isDiskCacheStrategySet()) { result = result.apply(diskCacheStrategyOf(DiskCacheStrategy.NONE)); } if (!result.requestOptions.isSkipMemoryCacheSet()) { result = result.apply(skipMemoryCacheOf(true /*skipMemoryCache*/)); } return result; }
We can see that in the class of RequestBuilder, the load method takes parameters such as Url, Drawable, and so on, which formally starts loading the original resources. All loads bring parameters into the loadGeneric () method, so let's click in and see.
@NonNull private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) { this.model = model; isModelSet = true; return this; }
Load Generic does not do any special processing, just save the incoming resources and set isModelSet to true. This isModelSet is set to tell the later into method resources have been set up, otherwise it will not do into processing, and throw the exception "You must call# load () before calling# in ()", which can be seen in the source code of into below.
Three, into
We analyzed the source code of Glide.with.load above. In fact, with and load are very simple. The main thing is to enter. Then we start the last step of the operation into, click into the source code, as follows:
/** * 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); }
In the code, we can first look at the annotations, which say that the into method is to set up the resources to be loaded, cancel all loads currently loaded into the view, release all resources loaded into the view before release, and then reuse them. Setting a resource to a specified location.
So let's analyze what's done in into. First you can see that a request Options object is captured in this method, and then the built-in parameters of request Options are set according to the type of view scaling. This code is not very important, so don't start describing it. The emphasis is on the last sentence in glideContext. build ImageViewTarget (view, transcodeClass).
/ TagetListener=/ null, request Options;, the into method passes in three parameters, the first is viewTarget, the second is targetListener, which doesn't matter, and the third is just acquired request Options. The first parameter, viewTarget, is used to place the final image. So let's first look at how viewTarget is captured. Enter the buildImageViewTarget () method:
@NonNull public <X> ViewTarget<ImageView, X> buildImageViewTarget( @NonNull ImageView imageView, @NonNull Class<X> transcodeClass) { return imageViewTargetFactory.buildTarget(imageView, transcodeClass); }
BuilImageViewTarget is just a transit station, and then we go to build Target:
/** * 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)"); } } }
As you can see clearly in the build Target, depending on the type of Class we pass in, we return different types of ViewTarget. For example, a resource of Bitmap type returns a ViewTarget of Bitmap ImageViewTarget type. Get the ViewTarget, then we'll go back to the in () method and follow up to see the following code:
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; }
If we look at this code, we can see that the isModelSet as true mentioned at the end of the with module is reflected in this code, that is to say, if the load does not set the resources properly, the next into will not be processed.
So let's go down and look at a piece of buildRequest code to build a Request, and the function of Request is to send a request to load a picture. Let's click in to see how Request is built.
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); }
You can see that buildRequestRecursive is called again in buildRequest, so we'll follow up.
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) { // Build the ErrorRequestCoordinator first if necessary so we can update parentCoordinator. ErrorRequestCoordinator errorRequestCoordinator = null; if (errorBuilder != null) { errorRequestCoordinator = new ErrorRequestCoordinator(parentCoordinator); parentCoordinator = errorRequestCoordinator; } Request mainRequest = buildThumbnailRequestRecursive( target, targetListener, parentCoordinator, transitionOptions, priority, overrideWidth, overrideHeight, requestOptions); if (errorRequestCoordinator == null) { return mainRequest; } int errorOverrideWidth = errorBuilder.requestOptions.getOverrideWidth(); int errorOverrideHeight = errorBuilder.requestOptions.getOverrideHeight(); if (Util.isValidDimensions(overrideWidth, overrideHeight) && !errorBuilder.requestOptions.isValidOverride()) { errorOverrideWidth = requestOptions.getOverrideWidth(); errorOverrideHeight = requestOptions.getOverrideHeight(); } Request errorRequest = errorBuilder.buildRequestRecursive( target, targetListener, errorRequestCoordinator, errorBuilder.transitionOptions, errorBuilder.requestOptions.getPriority(), errorOverrideWidth, errorOverrideHeight, errorBuilder.requestOptions); errorRequestCoordinator.setRequests(mainRequest, errorRequest); return errorRequestCoordinator; }
You can see that two Requests have been created in the build Request Recursive, one for errorRequest and the other for mainRequest. We mainly look at what is done in mainRequest.
MaiRequest is actually a request to process image thumbnails. The parameters in parentheses are also the type of thumbnails. Let's look at building Thumbnail Request Recursive, which processes thumbnails. Thumbnail processing is divided into three cases. We mainly look at the last case, no thumbnail. When we enter the obtainRequest, we can see that we call the SingleRequest.obtain method. We will follow up and have a look.
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, @Nullable List<RequestListener<R>> requestListeners, 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, requestListeners, requestCoordinator, engine, animationFactory); return request; }
You can see that request.init is called in your home code and follow up.
private void init( Context context, GlideContext glideContext, Object model, Class<R> transcodeClass, RequestOptions requestOptions, int overrideWidth, int overrideHeight, Priority priority, Target<R> target, RequestListener<R> targetListener, @Nullable List<RequestListener<R>> requestListeners, RequestCoordinator requestCoordinator, Engine engine, TransitionFactory<? super R> animationFactory) { this.context = context; this.glideContext = glideContext; this.model = model; this.transcodeClass = transcodeClass; this.requestOptions = requestOptions; this.overrideWidth = overrideWidth; this.overrideHeight = overrideHeight; this.priority = priority; this.target = target; this.targetListener = targetListener; this.requestListeners = requestListeners; this.requestCoordinator = requestCoordinator; this.engine = engine; this.animationFactory = animationFactory; status = Status.PENDING; }
It's clear at this point that it's just an initialization operation and an assignment operation, so let's not go on looking at it.
The construction of Request is really a bit long. After reading Request, we will go back to the previous into method.
target.setRequest(request); requestManager.track(target, request);
We can see that the newly created request is set to target in into, and then the requestManager calls the trace method, and we enter the trace.
void track(@NonNull Target<?> target, @NonNull Request request) { targetTracker.track(target); requestTracker.runRequest(request); }
First add the incoming target to the targetTracker, then start tracking the given request and point it into the runRequest.
/** * 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); } }
The code shows that if the current request is not stopped, the begin method of the request is called, otherwise the request is deleted. Other things needn't be ignored. We mainly look at the begin method.
@Override public void begin() { assertNotCallingCallbacks(); stateVerifier.throwIfRecycled(); startTime = LogTime.getLogTime(); if (model == null) { if (Util.isValidDimensions(overrideWidth, overrideHeight)) { width = overrideWidth; height = overrideHeight; } // Only log at more verbose log levels if the user has set a fallback drawable, because // fallback Drawables indicate the user expects null models occasionally. 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 we're restarted after we're complete (usually via something like a notifyDataSetChanged // that starts an identical request into the same Target or View), we can simply use the // resource and size we retrieved the last time around and skip obtaining a new size, starting a // new load etc. This does mean that users who want to restart a load because they expect that // the view size has changed will need to explicitly clear the View or Target before starting // the new load. 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)); } }
We see a lot of logical judgments in the code. The first case is that the model equals empty, and the model knows from the above that it is creating the resource that request s come in. If it is empty, it starts calling the onLoadFailed method. When the model is empty, we can see that ErrorPlaceholder placeholder is set in when the model is empty.
Then we find the onSizeReady () method where we really start loading.
/** * A callback method that should never be invoked directly. */ @Override public void onSizeReady(int width, int height) { stateVerifier.throwIfRecycled(); if (IS_VERBOSE_LOGGABLE) { logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime)); } 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)); } }
Firstly, the status is set as the status of the resource being queried, then the width and height of the image are obtained, and then the load method of engine is called.
/** * Starts a load for the given arguments. * * <p>Must be called on the main thread. * * <p>The flow for any request is as follows: * <ul> * <li>Check the current set of actively used resources, return the active resource if * present, and move any newly inactive resources into the memory cache.</li> * <li>Check the memory cache and provide the cached resource if present.</li> * <li>Check the current set of in progress loads and add the cb to the in progress load if * one is present.</li> * <li>Start a new load.</li> * </ul> * * <p>Active resources are those that have been provided to at least one request and have not yet * been released. Once all consumers of a resource have released that resource, the resource then * goes to cache. If the resource is ever returned to a new consumer from cache, it is re-added to * the active resources. If the resource is evicted from the cache, its resources are recycled and * re-used if possible and the resource is discarded. There is no strict requirement that * consumers release their resources so active resources are held weakly. * * @param width The target width in pixels of the desired resource. * @param height The target height in pixels of the desired resource. * @param cb The callback that will be called when the load completes. */ public <R> LoadStatus load( GlideContext glideContext, Object model, Key signature, int width, int height, Class<?> resourceClass, Class<R> transcodeClass, Priority priority, DiskCacheStrategy diskCacheStrategy, Map<Class<?>, Transformation<?>> transformations, boolean isTransformationRequired, boolean isScaleOnlyOrNoTransform, Options options, boolean isMemoryCacheable, boolean useUnlimitedSourceExecutorPool, boolean useAnimationPool, boolean onlyRetrieveFromCache, ResourceCallback cb) { Util.assertMainThread(); long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0; EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations, resourceClass, transcodeClass, options); EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable); if (active != null) { cb.onResourceReady(active, DataSource.MEMORY_CACHE); if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Loaded resource from active resources", startTime, key); } return null; } EngineResource<?> cached = loadFromCache(key, isMemoryCacheable); if (cached != null) { cb.onResourceReady(cached, DataSource.MEMORY_CACHE); if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Loaded resource from cache", startTime, key); } return null; } EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache); if (current != null) { current.addCallback(cb); if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Added to existing load", startTime, key); } return new LoadStatus(cb, current); } EngineJob<R> engineJob = engineJobFactory.build( key, isMemoryCacheable, useUnlimitedSourceExecutorPool, useAnimationPool, onlyRetrieveFromCache); DecodeJob<R> decodeJob = decodeJobFactory.build( glideContext, model, key, signature, width, height, resourceClass, transcodeClass, priority, diskCacheStrategy, transformations, isTransformationRequired, isScaleOnlyOrNoTransform, onlyRetrieveFromCache, options, engineJob); jobs.put(key, engineJob); engineJob.addCallback(cb); engineJob.start(decodeJob); if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Started new load", startTime, key); } return new LoadStatus(cb, engineJob); }
The first part of the code is mainly about caching operations, so we will not analyze it first, and then we will open a separate article to analyze Glide's caching. So let's go on and build an engineJob to open threads. Then we create a decodeJob to decode images. Then we call the start method of engineJob and pass the decodeJob as a parameter. So let's take a look at the start process.
public void start(DecodeJob<R> decodeJob) { this.decodeJob = decodeJob; GlideExecutor executor = decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor(); executor.execute(decodeJob); }
start just calls GlideExecutor's execute method, so let's follow up.
@Override public void execute(@NonNull Runnable command) { delegate.execute(command); }
GlideExecutor opens a new thread to load resources, so we move from the main thread to the sub-thread and go into the DecodeJob class to find the run method.
@Override public void run() { // This should be much more fine grained, but since Java's thread pool implementation silently // swallows all otherwise fatal exceptions, this will at least make it obvious to developers // that something is failing. GlideTrace.beginSectionFormat("DecodeJob#run(model=%s)", model); // Methods in the try statement can invalidate currentFetcher, so set a local variable here to // ensure that the fetcher is cleaned up either way. DataFetcher<?> localFetcher = currentFetcher; try { if (isCancelled) { notifyFailed(); return; } runWrapped(); } catch (Throwable t) { // Catch Throwable and not Exception to handle OOMs. Throwables are swallowed by our // usage of .submit() in GlideExecutor so we're not silently hiding crashes by doing this. We // are however ensuring that our callbacks are always notified when a load fails. Without this // notification, uncaught throwables never notify the corresponding callbacks, which can cause // loads to silently hang forever, a case that's especially bad for users using Futures on // background threads. if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "DecodeJob threw unexpectedly" + ", isCancelled: " + isCancelled + ", stage: " + stage, t); } // When we're encoding we've already notified our callback and it isn't safe to do so again. if (stage != Stage.ENCODE) { throwables.add(t); notifyFailed(); } if (!isCancelled) { throw t; } } finally { // Keeping track of the fetcher here and calling cleanup is excessively paranoid, we call // close in all cases anyway. if (localFetcher != null) { localFetcher.cleanup(); } GlideTrace.endSection(); } }
We mainly look at the runWrapped method and find that there are three different case s. Whatever we are, we will finally go into the runGenerators method. Let's go in and see.
private void runGenerators() { currentThread = Thread.currentThread(); startFetchTime = LogTime.getLogTime(); boolean isStarted = false; while (!isCancelled && currentGenerator != null && !(isStarted = currentGenerator.startNext())) { stage = getNextStage(stage); currentGenerator = getNextGenerator(); if (stage == Stage.SOURCE) { reschedule(); return; } } // We've run out of stages and generators, give up. if ((stage == Stage.FINISHED || isCancelled) && !isStarted) { notifyFailed(); } // Otherwise a generator started a new load and we expect to be called back in // onDataFetcherReady. }
Because there are too many details, we mainly look at the important part of the code, so take a look at the code in the while part, and you can see that the current Generator calls the startNext method, which is the startNext method in the SourceCacheGenerator class.
@Override public boolean startNext() { if (dataToCache != null) { Object data = dataToCache; dataToCache = null; cacheData(data); } if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) { return true; } sourceCacheGenerator = null; loadData = null; boolean started = false; while (!started && hasNextModelLoader()) { loadData = helper.getLoadData().get(loadDataListIndex++); if (loadData != null && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource()) || helper.hasLoadPath(loadData.fetcher.getDataClass()))) { started = true; loadData.fetcher.loadData(helper.getPriority(), this); } } return started; }
In while's logic, the code to get the data begins to appear, and DecodeHelper calls getLoadData to return a List collection of data, where it begins to get the loaded data.
Here, how do we get the data? Let's look at it carefully for a moment. When we get the data, then we'll go back to it.
In begin's method, you can see an onResourceReady method in the code. Let's go in and see what he did.
/** * Internal {@link #onResourceReady(Resource, DataSource)} where arguments are known to be safe. * * @param resource original {@link Resource}, never <code>null</code> * @param result object returned by {@link Resource#get()}, checked for type and never * <code>null</code> */ private void onResourceReady(Resource<R> resource, R result, DataSource dataSource) { // We must call isFirstReadyResource before setting status. boolean isFirstResource = isFirstReadyResource(); status = Status.COMPLETE; this.resource = resource; if (glideContext.getLogLevel() <= Log.DEBUG) { Log.d(GLIDE_TAG, "Finished loading " + result.getClass().getSimpleName() + " from " + dataSource + " for " + model + " with size [" + width + "x" + height + "] in " + LogTime.getElapsedMillis(startTime) + " ms"); } isCallingCallbacks = true; try { boolean anyListenerHandledUpdatingTarget = false; if (requestListeners != null) { for (RequestListener<R> listener : requestListeners) { anyListenerHandledUpdatingTarget |= listener.onResourceReady(result, model, target, dataSource, isFirstResource); } } anyListenerHandledUpdatingTarget |= targetListener != null && targetListener.onResourceReady(result, model, target, dataSource, isFirstResource); if (!anyListenerHandledUpdatingTarget) { Transition<? super R> animation = animationFactory.build(dataSource, isFirstResource); target.onResourceReady(result, animation); } } finally { isCallingCallbacks = false; } notifyLoadSuccess(); }
In the above code try, target calls onResourceReady, and does this target feel familiar to us? Yes, it's the viewTarget built by glideContext.buildImageViewTarget at the beginning, because it's just beginning to analyze, because different classes, target returns different types, divided into two kinds, one is Bitmap ImageViewTarget, the other is Drawabl. EImageViewTarget, so we can go into these two classes separately and see clearly
Both Targets inherit from ImageViewTarget, so let's look at their parent classes.
public abstract class ImageViewTarget<Z> extends ViewTarget<ImageView, Z> implements Transition.ViewAdapter { ... @Override public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) { if (transition == null || !transition.transition(resource, this)) { setResourceInternal(resource); } else { maybeUpdateAnimatable(resource); } } ... 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); } ... protected abstract void setResource(@Nullable Z resource); }
In the onResourceReady method, the setResourceInternal is called, and then the setResource method, which is an abstract method representing the settings image, is called. So how to set up the image needs to go to the subclass to see, we know that BitmapImageViewTarget and Drawable ImageViewTarget are his subclasses, and then with the table into a class, such as BitmapImageViewTarget.
public class BitmapImageViewTarget extends ImageViewTarget<Bitmap> { ... /** * Sets the {@link android.graphics.Bitmap} on the view using {@link * android.widget.ImageView#setImageBitmap(android.graphics.Bitmap)}. * * @param resource The bitmap to display. */ @Override protected void setResource(Bitmap resource) { view.setImageBitmap(resource); } }
In the setResource method, view calls the setImageBitmap method, that is to say, we finally set the image in the ImageView, so that the image is displayed in the specified location.
So far, Glide's process from with to load to into has been analyzed!!!
IV. Write at the end
Although there is only such a simple sentence, the source code of Glide is very complex and complex, and it takes a long time to sort out the process. The process of analyzing source code takes a lot of effort and effort, and more of it tests endurance. Here I want to thank Guo Linda for his analysis of Glide3 source code. Although Glide4 and Glide3 source code are different, they are very helpful for analysis. Thank you again!!! The basic analysis of Glide 4 is finished, so the next work is to analyze Glide's main caching mechanism.
Recommended reading
Glide 4 Parsing Series (1): How to Use Glide
Welcome to pay attention to my public number: Little ape said
Share and learn new technologies.