We finished analyzing the with () and load () methods in Part 1, and will continue the into () method analysis in Part 2.

  1. into()

If the first two steps were the hors d ‘oeuvres, now it’s time to get to the main course, because the into() method is also the most logically complex part of the Glide image loading process.

But as you can see from the code, there is no logic in the into() method, just super.into(view). Into () is in the parent class of the equestBuilder for drawablerRequest.

The parent class of The RequestBuilder is GenericRequestBuilder, and the into() method of the GenericRequestBuilder class is shown as follows:

public Target<TranscodeType> into(ImageView view) { Util.assertMainThread(); if (view == null) { throw new IllegalArgumentException("You must pass in a non null View"); } 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)); }Copy the code

We can leave all the judgment logic behind and explain it later when we talk about transform, but for now we just need to focus on the last line of code. . The last line of code first calls the glide buildImageViewTarget () method, this method can construct a Target object, the Target object is used to display images with eventually, if we go in to see the following code:

<R> Target<R> buildImageViewTarget(ImageView imageView, Class<R> transcodedClass) {
    return imageViewTargetFactory.buildTarget(imageView, transcodedClass);
}
Copy the code

Again, the ImageViewTargetFactory buildTarget() method is called, and the code looks like this:

public class ImageViewTargetFactory { @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)) { return (Target<Z>) new BitmapImageViewTarget(view); } else if (Drawable.class.isAssignableFrom(clazz)) { return (Target<Z>) new DrawableImageViewTarget(view); } else { throw new IllegalArgumentException("Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)"); }}}Copy the code

As you can see, the buildTarget() method builds different Target objects based on the class argument passed in. So if you want to analyze where this class parameter is coming from, that’s a lot of work for you to do, so I’ll just sort it out for you for simplicity. This class parameter is really only two things, if you call the asBitmap() method when you Glide load the image, then this will build the BitmapImageViewTarget object, Otherwise build are GlideDrawableImageViewTarget object. As for the DrawableImageViewTarget object in the code above, this is usually not needed, so we can forget about it for the moment.

. That is, by glide buildImageViewTarget () method, we construct a GlideDrawableImageViewTarget object. Going back to the last line of the into() method, you can see that this parameter is passed to the other GenericRequestBuilder into() method that receives the Target object. Into () :

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())"); } 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; }Copy the code

Here we’ll stick to the core code, but the only two lines that matter are line 15, which calls buildRequest() to build a Request object, and line 18, which executes the Request.

Request is used to make image loading requests and is a key component of Glide. Let’s start by looking at how the buildRequest() method builds the Request object:

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) { if (isThumbnailBuilt) { throw new IllegalStateException("You cannot use a request as both the main request and  a thumbnail, " + "consider using clone() on the request(s) passed to thumbnail()"); } if (thumbnailRequestBuilder.animationFactory.equals(NoAnimation.getFactory())) { thumbnailRequestBuilder.animationFactory = animationFactory; } if (thumbnailRequestBuilder.priority == null) { thumbnailRequestBuilder.priority = getThumbnailPriority(); } if (Util.isValidDimensions(overrideWidth, overrideHeight) && ! Util.isValidDimensions(thumbnailRequestBuilder.overrideWidth, thumbnailRequestBuilder.overrideHeight)) { thumbnailRequestBuilder.override(overrideWidth, overrideHeight); } ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator); Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator); isThumbnailBuilt = true; Request thumbRequest = thumbnailRequestBuilder.buildRequestRecursive(target, coordinator); isThumbnailBuilt = false; coordinator.setRequests(fullRequest, thumbRequest); return coordinator; } else if (thumbSizeMultiplier ! = null) { 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 { return obtainRequest(target, sizeMultiplier, priority, parentCoordinator); } } 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); }Copy the code

As you can see, buildRequest() actually calls buildRequestRecursive() inside the buildRequest() method. BuildRequestRecursive () is a bit long, but 90% of the code is working with thumbnails. If we just follow the main flow, we only need to look at line 47. The obtainRequest() method is called to obtain a Request object, and the GenericRequest obtain() method is called in the obtainRequest() method. Note that the obtain() method requires a lot of parameters — many of which are familiar — as in placeholderId, errorPlaceholder, diskCacheStrategy, and so on. Therefore, it is reasonable to assume that all the APIS just called in the load() method are actually assembled into the Request object here. So let’s get into the GenericRequest obtain() method and take a look:

public final class GenericRequest<A, T, Z, R> implements Request, SizeReadyCallback, ResourceCallback { ... public static <A, T, Z, R> GenericRequest<A, T, Z, R> obtain( LoadProvider<A, T, Z, R> loadProvider, A model, Key signature, Context context, Priority priority, Target<R> target, float sizeMultiplier, Drawable placeholderDrawable, int placeholderResourceId, Drawable errorDrawable, int errorResourceId, Drawable fallbackDrawable, int fallbackResourceId, RequestListener<? super A, R> requestListener, RequestCoordinator requestCoordinator, Engine engine, Transformation<Z> transformation, Class<R> transcodeClass, boolean isMemoryCacheable, GlideAnimationFactory<R> animationFactory, int overrideWidth, int overrideHeight, DiskCacheStrategy diskCacheStrategy) { @SuppressWarnings("unchecked") GenericRequest<A, T, Z, R> request = (GenericRequest<A, T, Z, R>) REQUEST_POOL.poll(); if (request == null) { request = new GenericRequest<A, T, Z, R>(); } request.init(loadProvider, model, signature, context, priority, target, sizeMultiplier, placeholderDrawable, placeholderResourceId, errorDrawable, errorResourceId, fallbackDrawable, fallbackResourceId, requestListener, requestCoordinator, engine, transformation, transcodeClass, isMemoryCacheable, animationFactory, overrideWidth, overrideHeight, diskCacheStrategy); return request; }... }Copy the code

As you can see, here a GenericRequest object is new at line 33 and returned at the last line, that is, the obtain() method is actually obtaining a GenericRequest object. GenericRequest init() is called on line 35, which is basically the assignment code that assigns these parameters to GenericRequest’s member variables, but we won’t follow through.

Ok, so now that we’ve solved the problem of building a Request object, let’s look at how the Request object is executed. Back to into () method, you will find in line 18 call requestTracker. RunRequest () method to execute the Request, then we go in with a look at, as shown below:

/**
 * Starts tracking the given request.
 */
public void runRequest(Request request) {
    requests.add(request);
    if (!isPaused) {
        request.begin();
    } else {
        pendingRequests.add(request);
    }
}
Copy the code

It is a simple logical judgment that Glide is currently processing the suspended state. If it is not, call Request begin() to execute the Request. If it is not, add the Request to the queue. Wait until the suspension is lifted.

The ability to pause requests is still not the concern of this article, so we’ll just ignore it and focus on the begin() method. Since the current Request object is a GenericRequest, we need to look at the begin() method in GenericRequest, as follows:

@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()); } if (Log.isLoggable(TAG, Log.VERBOSE)) { logV("finished run method in " + LogTime.getElapsedMillis(startTime)); }}Copy the code

First, if model is equal to null, which is the image URL we passed in the second load() method, onException() will be called. If you look inside the onException() method, you’ll see that it ends up calling a setErrorPlaceholder(), as follows:

private void setErrorPlaceholder(Exception e) { if (! canNotifyStatusChanged()) { return; } Drawable error = model == null ? getFallbackDrawable() : null; if (error == null) { error = getErrorDrawable(); } if (error == null) { error = getPlaceholderDrawable(); } target.onLoadFailed(e, error); }Copy the code

This method retrieves an error placeholder, or a loading placeholder if not available, and then calls target.onloadFailed () and passes the placeholder. So what does that do in the onLoadFailed() method? Let’s see:

public abstract class ImageViewTarget<Z> extends ViewTarget<ImageView, Z> implements GlideAnimation.ViewAdapter { ... @Override public void onLoadStarted(Drawable placeholder) { view.setImageDrawable(placeholder); } @Override public void onLoadFailed(Exception e, Drawable errorDrawable) { view.setImageDrawable(errorDrawable); }... }Copy the code

It is simply displaying the error placeholder in ImageView because there is an exception and the normal image cannot be displayed. If you look at line 15 of the begin() method, you’ll see that it calls a target.onloadStarted () method and passes in a loading map that will be used in place of the final image display before the image request begins. This is also the underlying implementation of placeholder() and Error () apis that we learned about in the previous article.

Ok, so let’s go back to the begin() method. Now that I’ve talked about the implementation of a placeholder map, where does the actual image loading start? Are in lines 10 and 12 of the begin() method. There are two cases where you specify a fixed width and height for the image using the Override () API, or not. If specified, line 10 is executed, calling the onSizeReady() method. If not specified, line 12 is executed, calling the target.getsize () method. The target.getSize() method internally does a series of calculations based on the layout_width and layout_height values of the ImageView to figure out how wide and high the image should be. I won’t go into all the details, but once it’s done, it also calls onSizeReady(). That is, in either case, the onSizeReady() method is eventually called, where the next step is taken. So let’s follow this method:

@Override 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; width = Math.round(sizeMultiplier * width); height = Math.round(sizeMultiplier * height); ModelLoader<A, T> modelLoader = loadProvider.getModelLoader(); final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height); if (dataFetcher == null) { onException(new Exception("Failed to load model: \'" + model + "\'")); return; } ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder(); if (Log.isLoggable(TAG, Log.VERBOSE)) { logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime)); } loadedFromMemoryCache = true; loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder, priority, isMemoryCacheable, diskCacheStrategy, this); loadedFromMemoryCache = resource ! = null; if (Log.isLoggable(TAG, Log.VERBOSE)) { logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime)); }}Copy the code

From here, the real complexity comes, and we need to slowly analyze it. Look at the first, in line 12 call loadProvider. GetModelLoader () method, we first need to understand is, what is this loadProvider? To figure this out, go back to the load() method in step 2. Remember that the load() method returns a DrawableTypeRequest object? DrawableTypeRequest (); DrawableTypeRequest (); DrawableTypeRequest (); DrawableTypeRequest (); DrawableTypeRequest (); DrawableTypeRequest (); DrawableTypeRequest ();

public class DrawableTypeRequest<ModelType> extends DrawableRequestBuilder<ModelType> implements DownloadOptions { private final ModelLoader<ModelType, InputStream> streamModelLoader; private final ModelLoader<ModelType, ParcelFileDescriptor> fileDescriptorModelLoader; private final RequestManager.OptionsApplier optionsApplier; private static <A, Z, R> FixedLoadProvider<A, ImageVideoWrapper, Z, R> buildProvider(Glide glide, ModelLoader<A, InputStream> streamModelLoader, ModelLoader<A, ParcelFileDescriptor> fileDescriptorModelLoader, Class<Z> resourceClass, Class<R> transcodedClass, ResourceTranscoder<Z, R> transcoder) { if (streamModelLoader == null && fileDescriptorModelLoader == null) { return null; } if (transcoder == null) { transcoder = glide.buildTranscoder(resourceClass, transcodedClass); } DataLoadProvider<ImageVideoWrapper, Z> dataLoadProvider = glide.buildDataProvider(ImageVideoWrapper.class, resourceClass); ImageVideoModelLoader<A> modelLoader = new ImageVideoModelLoader<A>(streamModelLoader, fileDescriptorModelLoader); return new FixedLoadProvider<A, ImageVideoWrapper, Z, R>(modelLoader, transcoder, dataLoadProvider); } DrawableTypeRequest(Class<ModelType> modelClass, ModelLoader<ModelType, InputStream> streamModelLoader, ModelLoader<ModelType, ParcelFileDescriptor> fileDescriptorModelLoader, Context context, Glide glide, RequestTracker requestTracker, Lifecycle lifecycle, RequestManager.OptionsApplier optionsApplier) { super(context, modelClass, buildProvider(glide, streamModelLoader, fileDescriptorModelLoader, GifBitmapWrapper.class, GlideDrawable.class, null), glide, requestTracker, lifecycle); this.streamModelLoader = streamModelLoader; this.fileDescriptorModelLoader = fileDescriptorModelLoader; this.optionsApplier = optionsApplier; }... }Copy the code

As you can see, here in line 29, that is, the constructor, calling a buildProvider () method, and the parameters such as streamModelLoader and fileDescriptorModelLoader passed to this method, These two ModelLoaders were previously built in the loadGeneric() method.

So let’s take a look at what’s going on in the buildProvider() method. We call Glide. BuildTranscoder () on line 16 to build a ResourceTranscoder that transcodes images, Since ResourceTranscoder is an interface that will actually build a GifBitmapWrapperDrawableTranscoder object here.

The glide.buildDataProvider() method is then called at line 18 to build a DataLoadProvider, which is used to encode and decode images. Since DataLoadProvider is an interface, Here the actual will construct a ImageVideoGifDrawableLoadProvider object.

Then in line 20, we create an instance of ImageVideoModelLoader and encapsulate the two ModelLoaders built in the loadGeneric() method into ImageVideoModelLoader.

Finally, on line 22, new produces a FixedLoadProvider, And just build up GifBitmapWrapperDrawableTranscoder, ImageVideoModelLoader, ImageVideoGifDrawableLoadProvider are encapsulated inside, This is the loadProvider in the onSizeReady() method.

Okay, so let’s go back to the onSizeReady() method, and in line 12 and line 18 of the onSizeReady() method, GetModelLoader () and getTranscoder() methods of loadProvider are called, respectively. So the object is our analysis ImageVideoModelLoader and GifBitmapWrapperDrawableTranscoder just now. On line 13, the ImageVideoModelLoader’s getResourceFetcher() method is called. Here we need to look again:

public class ImageVideoModelLoader<A> implements ModelLoader<A, ImageVideoWrapper> { private static final String TAG = "IVML"; private final ModelLoader<A, InputStream> streamLoader; private final ModelLoader<A, ParcelFileDescriptor> fileDescriptorLoader; public ImageVideoModelLoader(ModelLoader<A, InputStream> streamLoader, ModelLoader<A, ParcelFileDescriptor> fileDescriptorLoader) { if (streamLoader == null && fileDescriptorLoader == null) { throw new NullPointerException("At least one of streamLoader and fileDescriptorLoader must be non null"); } this.streamLoader = streamLoader; this.fileDescriptorLoader = fileDescriptorLoader; } @Override public DataFetcher<ImageVideoWrapper> getResourceFetcher(A model, int width, int height) { DataFetcher<InputStream> streamFetcher = null; if (streamLoader ! = null) { streamFetcher = streamLoader.getResourceFetcher(model, width, height); } DataFetcher<ParcelFileDescriptor> fileDescriptorFetcher = null; if (fileDescriptorLoader ! = null) { fileDescriptorFetcher = fileDescriptorLoader.getResourceFetcher(model, width, height); } if (streamFetcher ! = null || fileDescriptorFetcher ! = null) { return new ImageVideoFetcher(streamFetcher, fileDescriptorFetcher); } else { return null; } } static class ImageVideoFetcher implements DataFetcher<ImageVideoWrapper> { private final DataFetcher<InputStream> streamFetcher; private final DataFetcher<ParcelFileDescriptor> fileDescriptorFetcher; public ImageVideoFetcher(DataFetcher<InputStream> streamFetcher, DataFetcher<ParcelFileDescriptor> fileDescriptorFetcher) { this.streamFetcher = streamFetcher; this.fileDescriptorFetcher = fileDescriptorFetcher; }... }}Copy the code

As you can see, in the guild calls first 20 streamLoader. GetResourceFetcher () method to obtain a DataFetcher, This streamLoader is actually the StreamStringLoader we built in the loadGeneric() method. Calling its getResourceFetcher() method returns an HttpUrlFetcher object. Then at line 28, new generates an ImageVideoFetcher object and passes in the HttpUrlFetcher object it got. That is, the getResourceFetcher() method of ImageVideoModelLoader gets an ImageVideoFetcher.

So let’s go back to the onSizeReady() method, line 23 of the onSizeReady() method, It will just get ImageVideoFetcher, GifBitmapWrapperDrawableTranscoder and so on a series of values to the Engine load () method. What’s going on in this Engine’s load() method? The code looks like this:

public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener { ... public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher, DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder, Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) { Util.assertMainThread(); long startTime = LogTime.getLogTime(); final String id = fetcher.getId(); EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(), loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(), transcoder, loadProvider.getSourceEncoder()); EngineResource<? > cached = loadFromCache(key, isMemoryCacheable); if (cached ! = null) { cb.onResourceReady(cached); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Loaded resource from cache", startTime, key); } return null; } EngineResource<? > active = loadFromActiveResources(key, isMemoryCacheable); if (active ! = null) { cb.onResourceReady(active); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Loaded resource from active resources", startTime, key); } return null; } EngineJob current = jobs.get(key); if (current ! = null) { current.addCallback(cb); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Added to existing load", startTime, key); } return new LoadStatus(cb, current); } EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable); DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation, transcoder, diskCacheProvider, diskCacheStrategy, priority); EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority); jobs.put(key, engineJob); engineJob.addCallback(cb); engineJob.start(runnable); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Started new load", startTime, key); } return new LoadStatus(cb, engineJob); }... }Copy the code

The code in the load() method is a bit long, but most of the code deals with caching. We’ll learn more about caching in the next article, but just start at line 45. A EngineJob is built to start the thread in preparation for asynchronously loading images later. Next line 46 creates a DecodeJob object that, by its name, looks like it’s used to decode images, but is actually doing a lot of work, as we’ll see. Moving on, an EngineRunnable object is created at line 48, and the EngineJob start() method is called at line 51 to run the EngineRunnable object. This essentially makes the Run () method of EngineRunnable execute in the child thread. So we can now look at what EngineRunnable’s run() method does, as follows:

@Override public void run() { if (isCancelled) { return; } Exception exception = null; Resource<? > resource = null; try { resource = decode(); } catch (Exception e) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Exception decoding", e); } exception = e; } if (isCancelled) { if (resource ! = null) { resource.recycle(); } return; } if (resource == null) { onLoadFailed(exception); } else { onLoadComplete(resource); }}Copy the code

There’s not a lot of code in this method, but it’s still important to focus on. In line 9, a decode() method is called, and this method returns a Resource object. It looks like all the logic should be done in the decode() method, so let’s take a look:

private Resource<?> decode() throws Exception {
    if (isDecodingFromCache()) {
        return decodeFromCache();
    } else {
        return decodeFromSource();
    }
}
Copy the code

DecodeFromCache () is executed if a decode image is removed from the cache, and decodeFromSource() is executed otherwise. We won’t discuss caching in this article, so let’s go straight to the decodeFromSource() method, as shown below:

private Resource<? > decodeFromSource() throws Exception { return decodeJob.decodeFromSource(); }Copy the code

The decodeFromSource() method of the DecodeJob is called. As we have said, DecodeJob has a very heavy task, so let’s continue to follow up and have a look:

class DecodeJob<A, T, Z> { ... public Resource<Z> decodeFromSource() throws Exception { Resource<T> decoded = decodeSource(); return transformEncodeAndTranscode(decoded); } private Resource<T> decodeSource() throws Exception { Resource<T> decoded = null; try { long startTime = LogTime.getLogTime(); final A data = fetcher.loadData(priority); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Fetched data", startTime); } if (isCancelled) { return null; } decoded = decodeFromSourceData(data); } finally { fetcher.cleanup(); } return decoded; }... }Copy the code

These are the main methods, and I’ve extracted them for you. So let’s look at the decodeFromSource() method. The first step is to call the decodeSource() method to get a Resource object. The second step is to call transformEncodeAndTranscode () method to deal with this Resource object.

Due to limited space, the analysis in the last part of this article will be presented to you in the next article. You are welcome to participate in the discussion.