This article Glide source code based on 4.9, version download address is as follows: Glide 4.9

preface

Glide source code is really complex, so this article only analyzes and posts functions and code related to the image loading process. In addition, this article Glide source code based on 4.9, and 3.x source code or there are differences, but the overall process changes little.

Glide for this powerful Android picture loading open source framework, I believe that we are not unfamiliar with it, anyway, the author’s words, the normal project with the picture loading framework is mostly it, because it is really very convenient and fast, with convenient, but it really shows that its source is so simple? So today want to uncover the mystery of Glide, Glide from the source to analyze the picture loading process.

In most cases, we want to load and display an image in the interface with only one line of code, as follows:

Glide.with(this).load(url).into(imageView);
Copy the code

So we Glide picture loading process of the source analysis can be divided into three:

  • with
  • load
  • into

Let’s play the trilogy together!

One, with

1. The role

  1. Get the RequestManager object
  2. An object that performs a series of operations (load, codec, transcode) on an image is pre-created and added to Glide’s registry
  3. The life cycle for the image will Glide and Appliction/Activity/fragments binding

2. Process

2.1 a sequence diagram

First of all, we know from the use, the first song we call is Glide with method, so let’s have a look at this method first

Glide#with

  /** * Application type */
  public static RequestManager with(@NonNull Context context) {
  	//getRetriever returns the singleton object in the RequestManagerRetriever
  	/ / RequestManagerRetriever get returns RequestManager objects and bind the image loaded life cycle
    return getRetriever(context).get(context);
  }

  /**
   * 非Application类型
   */
  public static RequestManager with(@NonNull Activity activity) {
  	// As with the Application type, the RequestManagerRetriever get is called to obtain the RequestManager object
  	// Note that the parameter passed here is Activity
    return getRetriever(activity).get(activity);
  }

  public static RequestManager with(@NonNull FragmentActivity activity) {
    returngetRetriever(activity).get(activity); }...Copy the code

Can be found that with the method is a set of static methods on the Glide class, there are a lot of overloaded methods in the Glide, can be introduced into the Context, the Activity, fragments, etc., and then with the implementation of the inside is very simple, just a code, the return type, you will know its function is doing, Return a RequestManager object. So how exactly to get this object, let’s see!

2.2 Obtaining a RequestManagerRetriever Object

The RequestManagerRetriever object will be returned before you return it. Regardless of the parameter in the With retriever, the getRetriever method will be called and no methods will be overloaded. So the steps to get the RequestManagerRetriever object are the same, so let’s track down exactly how you got it.

  private static RequestManagerRetriever getRetriever(@Nullable Context context) {...Call Glide. Get to obtain the Glide object, in which the RequestManagerRetriever object is encapsulated
	/ / 2. Through the Glide getRequestManagerRetriever () to obtain RequestManagerRetriever object
    return Glide.get(context).getRequestManagerRetriever();
  }
 
  public static Glide get(@NonNull Context context) {
    if (glide == null) {
      synchronized (Glide.class) {
        if (glide == null) {
          // Focus oncheckAndInitializeGlide(context); }}}return glide;
  }


  private static void checkAndInitializeGlide(@NonNull Context context) {
    if (isInitializing) {
	  // This exception will be thrown if two initializations are performed simultaneously
      throw new IllegalStateException("You cannot call Glide.get() in registerComponents(),"
          + " use the provided Glide instance instead");
    }
    isInitializing = true;
	// Perform initialization
    initializeGlide(context);
    isInitializing = false;
  }
  private static void initializeGlide(@NonNull Context context, @NonNull GlideBuilder builder) {...GlideBuilder = GlideBuilder; GlideBuilder = GlideBuilderGlide glide = builder.build(applicationContext); . }Copy the code

First of all, the getRetriever method looks very similar to the code in with, but it does two things:

  • Obtain the Glide object through Glide. Get
  • Invoke the Glide getRequestManagerRetriever () get to RequestManagerRetriever object

Glide’s GET method is a simple standard singleton implementation. GlideBuilder: GlideBuilder: GlideBuilder: GlideBuilder: GlideBuilder: GlideBuilder: GlideBuilder: GlideBuilder: GlideBuilder

  @NonNull
  Glide build(@NonNull Context context) {...// Build an execution engine that manages thread pools and caches
    if (engine == null) {
      engine =
          new Engine(
              memoryCache,
              diskCacheFactory,
              diskCacheExecutor,
              sourceExecutor,
              GlideExecutor.newUnlimitedSourceExecutor(),
              GlideExecutor.newAnimationExecutor(),
              isActiveResourceRetentionAllowed);
    }

	// A RequestManagerRetriever object is built
    RequestManagerRetriever requestManagerRetriever =
        new RequestManagerRetriever(requestManagerFactory);

	// Build the Glide object and encapsulate the many thread pools and RequestManagerRetriever objects above
    return newGlide( context, engine, memoryCache, bitmapPool, arrayPool, requestManagerRetriever, connectivityMonitorFactory, logLevel, defaultRequestOptions.lock(), defaultTransitionOptions, defaultRequestListeners, isLoggingRequestOriginsEnabled); }}public class Glide implements ComponentCallbacks2 {
      Glide(
      @NonNull Context context,
      @NonNull Engine engine,
      @NonNull MemoryCache memoryCache,
      @NonNull BitmapPool bitmapPool,
      @NonNull ArrayPool arrayPool,
      @NonNull RequestManagerRetriever requestManagerRetriever,
      @NonNull ConnectivityMonitorFactory connectivityMonitorFactory,
      int logLevel,
      @NonNull RequestOptions defaultRequestOptions,
      @NonNullMap<Class<? >, TransitionOptions<? ,? >> defaultTransitionOptions,@NonNull List<RequestListener<Object>> defaultRequestListeners,
      boolean isLoggingRequestOriginsEnabled) {
          ...
          // Assign the RequestManagerRetriever object to a member variable
          this.requestManagerRetriever = requestManagerRetriever; ./ / decoder
          StreamBitmapDecoder streamBitmapDecoder = new StreamBitmapDecoder(downsampler, arrayPool);    
          // Add to the registry
          registry
              .append(Registry.BUCKET_BITMAP, InputStream.class, Bitmap.class, streamBitmapDecoder)
              ....
              /* Models */
              // Focus on InputStreamRewinder
              .register(new InputStreamRewinder.Factory(arrayPool))
              ....
              / / focus on StringLoader. StreamFactory ()
              .append(String.class, InputStream.class, new StringLoader.StreamFactory())
              .... 
              // Focus on httpuriloader.factory ()
        	 .append(Uri.class, InputStream.class, new HttpUriLoader.Factory())
              ....
              // Focus on HttpGlideUrlLoader
              .append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
              ....
              /* Transcoders */
	    	 // Focus on BitmapDrawableTranscoder
        	 .register(
            	 Bitmap.class,
            	 BitmapDrawable.class,
            	 newBitmapDrawableTranscoder(resources)) ..... }}Copy the code

Glide object creation from the above can be seen to do a lot of things, is also extremely complex, in general its responsibilities are as follows:

  • Create the RequestManagerRetriever object
  • Create an execution Engine object that manages thread pools and caches (needed below)
  • Build Registry and register many codecs (needed below)

In The registry, the above code lists only a few codecs that will be used below, but there are many more things in the registry. Let’s reconfirm that our goal is to get the RequestManagerRetriever object. In the code above, we have created a New RequestManagerRetriever object and assigned it to a Member variable of the Glide. Can then be obtained by Glide getRequestManagerRetriever to this RequestManagerRetriever object.

2.3 Obtaining a RequestManager Object

Let’s revisit one of the with methods

RequestManagerRetriever#with

  public static RequestManager with(@NonNull Context context) {
    return getRetriever(context).get(context);
  }
Copy the code

Once you have obtained the RequestManagerRetriever object, you need to obtain the RequestManager object using the Get method in the RequestManagerRetriever retriever. In the RequestManagerRetriever class, get, like Glide’s with, also has a number of overloaded methods. Overloaded methods handle different parameters differently and can be divided into two types of parameters based on the type of parameters involved:

  • The Application type
  • Non-application type (Activity/Fragment)

2.3.1 Obtaining the RequestManager object of Application Type

RequestManagerRetriever#get

  / / Application type
  public RequestManager get(@NonNull Context context) {
    if (context == null) {
      throw new IllegalArgumentException("You cannot start a load on a null Context");
    } else if(Util.isOnMainThread() && ! (contextinstanceof Application)) {
      // If context is in the main thread and not of Application type
      if (context instanceof FragmentActivity) {
        return get((FragmentActivity) context);
      } else if (context instanceof Activity) {
        return get((Activity) context);
      } else if (context instanceof ContextWrapper) {
        returnget(((ContextWrapper) context).getBaseContext()); }}// Get a RequestManager object if not in the main thread or for a call to getApplicationManager of Application type
    return getApplicationManager(context);
  }
Copy the code

You can see that get with context is handled in two ways:

  1. If you’re on the main thread and the context is not of type Application you call a get method that is not of type Application
  2. Call getApplicationManager to get the RequestManager object if it is not on the main thread or of Application type

Here we are looking at the Application type, so look directly at the getApplicationManager method

RequestManagerRetriever#getApplicationManager

  private RequestManager getApplicationManager(@NonNull Context context) {
    if (applicationManager == null) {
      synchronized (this) {
        if (applicationManager == null) {
          // The get method is to get the singleton object of Glide,
          // It is not necessary to create a singleton object for Glide because the singleton object for Glide is already created
          Glide glide = Glide.get(context.getApplicationContext());
          applicationManager =
              factory.build(
                  glide,
                  new ApplicationLifecycle(),
                  newEmptyRequestManagerTreeNode(), context.getApplicationContext()); }}}return applicationManager;
  }

  public interface RequestManagerFactory {
    @NonNull
    RequestManager build( @NonNull Glide glide, @NonNull Lifecycle lifecycle, @NonNull RequestManagerTreeNode requestManagerTreeNode, @NonNull Context context);
  }

  private static final RequestManagerFactory DEFAULT_FACTORY = new RequestManagerFactory() {
    @NonNull
    @Override
    public RequestManager build(@NonNull Glide glide, @NonNull Lifecycle lifecycle, @NonNull RequestManagerTreeNode requestManagerTreeNode, @NonNull Context context) {
      return newRequestManager(glide, lifecycle, requestManagerTreeNode, context); }}; }Copy the code

Glide get is a singleton implementation, so get the Glide object directly and new an ApplicationLifecycle. It then passes Glide and ApplicationLifecycle objects, etc., and creates a RequestManager object to bind to the Application lifecycle.

So why is Glide directly tied to the Application lifecycle?

This is because the life cycle of the Application object is the life cycle of the App, so the life cycle of Glide load image is directly tied to the life cycle of the Application, no special processing is required.

2.3.2 A Non-Application RequestManager object is Obtained

We will only use the Activity type as the parameter here, because other non-application types are basically similar to activities.

RequestManagerRetriever#get

  /**
   * 非Application类型
   */
  public RequestManager get(@NonNull FragmentActivity activity) {
    if (Util.isOnBackgroundThread()) {
	  // Call Aplication GET in child threads
      return get(activity.getApplicationContext());
    } else {
	  // Determine whether the Activity is destroyed
      assertNotDestroyed(activity);
	  // Get the FragmentManager object
      FragmentManager fm = activity.getSupportFragmentManager();
	  // Return RequestManager with a call to supportFragmentGet
      return supportFragmentGet(
          activity, fm, /*parentHint=*/ null, isActivityVisible(activity)); }}Copy the code

For non-application types, the first step is to determine whether the request is in the main thread or in a child thread. If it is in a child thread, the get method of the Application type is called, which is understandable because the Glide life cycle in the child thread should be the same as the Application life cycle.

If it is in the main thread, the supportFragmentGet method is called to bind to the Activity’s life cycle.

RequestManagerRetriever#supportFragmentGet

  private RequestManager supportFragmentGet(
      @NonNull Context context,
      @NonNull FragmentManager fm,
      @Nullable Fragment parentHint,
      boolean isParentVisible) {
    / / get SupportRequestManagerFragment
    SupportRequestManagerFragment current =
        getSupportRequestManagerFragment(fm, parentHint, isParentVisible);
    // Implement creation, add Fragment
    RequestManager requestManager = current.getRequestManager();
	// Initialize the requestManager if it is loaded for the first time
    if (requestManager == null) { 
      Glide glide = Glide.get(context);
      requestManager =
          factory.build(
              glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
	  / / set to SupportRequestManagerFragment
	  current.setRequestManager(requestManager);
    }
    return requestManager;
  }
Copy the code

How does supportFragmentGet bind to an Activity? The process is as follows:

  1. Create a hidden Fragment
  2. Add a hidden Fragment to the current Activity
  3. Create a RequestManager object and bind the RequestManager to the hidden Fragment’s life cycle

Why, you might ask, can you bind an Activity to its hidden Fragment life cycle? This is because the Fragment life cycle is synchronized with the Activity, so the hidden Fragment bound to it can listen to the Activity life cycle, so that it can synchronize the life cycle of Glide loading image with the Activity. It also avoids memory leaks when Glide holds an instance of an Activity.

3. Summary

This concludes the work of WITH, and lets summarize the main work of with

  • Building a Glide Object
    • Create an execution Engine object that manages thread pools and caches
    • Registry is built and many codecs are registered
    • Build the RequestManagerRetriever object
  • Build the RequestManager object
    • If the parameter in the child thread loading picture or with is of Application type, the Glide picture loading life cycle is bound to the Application life cycle
    • Otherwise, Glide image load life cycle is bound to the Activity or Fragment life cycle by adding a hidden Fragment to the current Activity/Fragment and binding the hidden Fragment life cycle.

Second, the load

1. The role

Create an image load request with a Drawable target, passing in the resources to be loaded (String,URL,URI, etc.)

2. Process

2.1 a sequence diagram

2.2 Creating a RequestBuilder Object

From the analysis of with, we know that with eventually returns a RequestManager object, so the second part begins with a Load method of the RequestManager.

RequestManager#load

  public RequestBuilder<Drawable> load(@Nullable String string) {
  	//1. AsDrawable creates an image loading request whose target is Drawable
  	//2. Call load to pass in the loaded resource
    return asDrawable().load(string);
  }

  public RequestBuilder<Drawable> load(@Nullable Uri uri) {
    return asDrawable().load(uri);
  }

  public RequestBuilder<Drawable> load(@Nullable URL url) {
    returnasDrawable().load(url); }...Copy the code

Load can also be overloaded in RequestManager, but we will only examine the most common load parameter for loading images, load(String URL).

In the Load method of the RequestManager, asDrawable is called first, so let’s look at asDrawable

  public RequestBuilder<Drawable> asDrawable(a) {
    return as(Drawable.class);
  }

  public <ResourceType> RequestBuilder<ResourceType> as( @NonNull Class
       
         resourceClass)
        {
    return new RequestBuilder<>(glide, this, resourceClass, context);
  }
Copy the code

The above code is simple, creating a picture load RequestBuilder whose target is Drawable.

2.3 Importing the IMAGE URL

Since asDrawable returns the RequestBuilder object, the load method of RequesBuilder will be called next

RequesBuilder#load

  public RequestBuilder<TranscodeType> load(@Nullable String string) {
    return loadGeneric(string);
  }

  private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
  	// Assign data to static member variables of the RequestBuilder
    this.model = model;
    isModelSet = true;
    return this;
  }
Copy the code

The code above is easy to understand. Load calls the loadGeneric method, which takes the data and assigns the Model of type String to the static member variable of the RequestBuilder.

3. Summary

Load is probably the simplest song in the trilogy, with simple code and easy to understand. This part is also one of the differences between 3.x and 4.9. In the 3.x source code, load should also perform a task, that is, pre-create an object that performs a series of operations on the image (load, codec, transcode). Through the above analysis of with, we know that in the source code of 4.9, this work has been given to with, so load compared with the other two, its work is relatively simple.

Three, into

!!!!!!!!! High warning, into source code analysis will take a long, long time

1. The role

In the child thread, the network requests the image to be parsed and returned to the main thread to display the image

2. Process

The following source code is based on the load parameter String, do not take memory cache, disk cache case

Load returns a RequestBuilder, so the entry to into is a RequestBuilder

RequestBuilder#into

public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {...// Return the ViewTarget object
    return into(
    	//glideContext Is of the glideContext type
        glideContext.buildImageViewTarget(view, transcodeClass),
        /*targetListener=*/ null,
        requestOptions,
        // A thread pool with bound main thread handlers
        Executors.mainThreadExecutor());
}
Copy the code

In the above code, we know that the parameters to be passed are obtained before calling into, so we focus on the first and fourth parameters.

2.1 Creating a ViewTarget Object

Let’s first analyze GlideContext’s buildImageViewTarget method.

GlideContext#buildImageViewTarget

  public <X> ViewTarget<ImageView, X> buildImageViewTarget( @NonNull ImageView imageView, @NonNull Class
       
         transcodeClass)
        {
    return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
  }
Copy the code

The transcodeClass passed in is actually the Drawable. Class we passed in the asDrawable we analyzed in part 2, and we continue to call the buildTarget method of ImageViewTargetFactory.

ImageViewTargetFactory#buildTarget

  public <Z> ViewTarget<ImageView, Z> buildTarget(@NonNull ImageView view, @NonNull Class
       
         clazz)
        {
    if (Bitmap.class.equals(clazz)) {
	  // If the asBitmap method is called
      return (ViewTarget<ImageView, Z>) new BitmapImageViewTarget(view);
    } else if (Drawable.class.isAssignableFrom(clazz)) {
      / / otherwise
      return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);
    } else {
      throw new IllegalArgumentException(
          "Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)"); }}Copy the code

Since we are not calling the asBitmap method and are passing in a Drawable, the ViewTarget object returned should be a DrawableImageViewTarget, which we will use when displaying images.

!!!!!!!!! Note: All targets that appear in the code below are DrawableImageViewTarget objects unless otherwise specified.

2.2 create MAIN_THREAD_EXECUTOR

Let’s go back to the into method.

  public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {...// Return the ViewTarget object
    return into(
    	BuildImageViewTarget Creates a ViewTarget object
    	//transcodeClass calls asBitmap and returns BitmapImageViewTarget
	    // Otherwise transcodeClass is of type Drawable and returns DrawableImageViewTarget
        glideContext.buildImageViewTarget(view, transcodeClass),
        /*targetListener=*/ null,
        requestOptions,
        // A thread pool with bound main thread handlers
        Executors.mainThreadExecutor());
  }
Copy the code

Let’s move on to the fourth parameter

Executors#mainThreadExecutor

private static final Executor MAIN_THREAD_EXECUTOR =
      new Executor() {
       // Bind the main thread Looper
        private final Handler handler = new Handler(Looper.getMainLooper());

        @Override
        public void execute(@NonNull Runnable command) { handler.post(command); }};public static Executor mainThreadExecutor(a) {
    return MAIN_THREAD_EXECUTOR;
  }
Copy the code

MainThreadExecutor returns MAIN_THREAD_EXECUTOR, which declares a Handler bound to the main thread Looper. The thread pool’s execute method then executes handler’s POST method, which is equivalent to executing Command’s Run method on the main thread. (This is a thread pool, because it will be reanalyzed when the main thread is displayed in the main thread.)

After analyzing the two arguments to into, let’s look at the overloaded into method

RequestBuilder#into

  private <Y extends Target<TranscodeType>> Y into( @NonNull Y target, @Nullable RequestListener
       
         targetListener, BaseRequestOptions
         options, Executor callbackExecutor)
        {
    Preconditions.checkNotNull(target);
    if(! isModelSet) {throw new IllegalArgumentException("You must call #load() before calling #into()");
    }
    // Build the Requset object and issue a request to load the image
    // Notice that the fourth argument is passed in the thread pool containing the Handler bound to the main thread
    Request request = buildRequest(target, targetListener, options, callbackExecutor);
    // Release the target object's existing request before starting
    Request previous = target.getRequest();
    if(request.isEquivalentTo(previous) && ! isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) { request.recycle();if(! Preconditions.checkNotNull(previous).isRunning()) { previous.begin(); }return target;
    }
		
    requestManager.clear(target);

	// Set the request to target
    target.setRequest(request);
	// Distribute and execute network Request requests, in which case the requestManager is the Request Manager
    requestManager.track(target, request);

    return target;
  }
Copy the code

We can find that in this method, in fact, there are two main tasks: one is to construct the Request of the network Request, and the other is to execute the Request object of the network Request. Next, we will analyze these two tasks respectively.

2.3 Constructing a Network Request Object Request

RequestBuilder#buildRequest

  private Request buildRequest( Target
       
         target, @Nullable RequestListener
        
          targetListener, BaseRequestOptions
          requestOptions, Executor callbackExecutor)
        
        {
    // Focus on the buildRequestRecursive method
    return buildRequestRecursive(
        target,
        targetListener,
        /*parentCoordinator=*/ null,
        transitionOptions,
        requestOptions.getPriority(),
        requestOptions.getOverrideWidth(),
        requestOptions.getOverrideHeight(),
        requestOptions,
        callbackExecutor);
  }

  private Request buildRequestRecursive(
      Target<TranscodeType> target,
      @Nullable RequestListener<TranscodeType> targetListener,
      @Nullable RequestCoordinator parentCoordinator,
      TransitionOptions<?, ? super TranscodeType> transitionOptions,
      Priority priority,
      int overrideWidth,
      intoverrideHeight, BaseRequestOptions<? > requestOptions, Executor callbackExecutor) {.../ / focus on buildThumbnailRequestRecursive method
    Request mainRequest =
        buildThumbnailRequestRecursive(
            target,
            targetListener,
            parentCoordinator,
            transitionOptions,
            priority,
            overrideWidth,
            overrideHeight,
            requestOptions,
            callbackExecutor);

    if (errorRequestCoordinator == null) {
      returnmainRequest; }... }private Request buildThumbnailRequestRecursive(
      Target<TranscodeType> target,
      RequestListener<TranscodeType> targetListener,
      @Nullable RequestCoordinator parentCoordinator,
      TransitionOptions<?, ? super TranscodeType> transitionOptions,
      Priority priority,
      int overrideWidth,
      intoverrideHeight, BaseRequestOptions<? > requestOptions, Executor callbackExecutor) {...// Focus on key codeRequest fullRequest = obtainRequest( target, targetListener, requestOptions, coordinator, transitionOptions, priority, overrideWidth, overrideHeight, callbackExecutor); . }private Request obtainRequest( Target<TranscodeType> target, RequestListener<TranscodeType> targetListener, BaseRequestOptions<? > requestOptions, RequestCoordinator requestCoordinator, TransitionOptions<? ,?super TranscodeType> transitionOptions,
      Priority priority,
      int overrideWidth,
      int overrideHeight,
      Executor callbackExecutor) {
    // The Obtain method of SingleRequest is called to assemble all the API parameters called in load into the Request object
    // The callbackExecutor is a thread pool with a bound main thread Handler
    return SingleRequest.obtain(
        context,
        glideContext,
        model,
        transcodeClass,
        requestOptions,
        overrideWidth,
        overrideHeight,
        priority,
        target,
        targetListener,
        requestListeners,
        requestCoordinator,
        glideContext.getEngine(),
        transitionOptions.getTransitionFactory(),
        callbackExecutor);
  }
Copy the code

Step by step, the Obtain method of SingleRequest will eventually be executed, so let’s continue with this method

SingleRequest# obtain method

  public static <R> SingleRequest<R> obtain( Context context, GlideContext glideContext, Object model, Class<R> transcodeClass, BaseRequestOptions<? > 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,
      Executor callbackExecutor) {
    @SuppressWarnings("unchecked") SingleRequest<R> request =
        (SingleRequest<R>) POOL.acquire();
    if (request == null) {
	  // Create SingleRequest object
      request = new SingleRequest<>();
    }
	// Assign the API arguments passed in load to the SingleRequest member variable
	// The last argument is the thread pool for the master thread
    request.init(
        context,
        glideContext,
        model,
        transcodeClass,
        requestOptions,
        overrideWidth,
        overrideHeight,
        priority,
        target,
        targetListener,
        requestListeners,
        requestCoordinator,
        engine,
        animationFactory,
        callbackExecutor);
    return request;
  }
  // Assign to a member variable
  private synchronized void init( Context context, GlideContext glideContext, Object model, Class<R> transcodeClass, BaseRequestOptions<? > 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,
      Executor callbackExecutor) {
    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;
    this.callbackExecutor = callbackExecutor;
    status = Status.PENDING;

    if (requestOrigin == null && glideContext.isLoggingRequestOriginsEnabled()) {
      requestOrigin = new RuntimeException("Glide request origin trace"); }}Copy the code

The Obtain method essentially creates the SingleRequest object and then calls the init method to assign values to the member variables, so the network request object is the SingleRequest object.

2.4 Executing the Network Request object Request

Let’s go back to the into method

  private <Y extends Target<TranscodeType>> Y into( @NonNull Y target, @Nullable RequestListener
       
         targetListener, BaseRequestOptions
         options, Executor callbackExecutor)
        {
    // Build the Requset object and issue a request to load the image
    // Finally build the SingleRequest objectRequest request = buildRequest(target, targetListener, options, callbackExecutor); .// Distribute and execute network Request requests, in which case the requestManager is the requestManager object
    //target is the DrawableImageViewTarget created above (refer back to 2.1 if you forget)
    // Request is the completed singleRequest object
    requestManager.track(target, request);

    return target;
  }
Copy the code

At this point we have successfully built the SingleRequest object and then called RequestManager’s Track method to distribute and execute the request

Against 2.4.1 before loading

RequestManager#track

  synchronized void track(@NonNull Target
        target, @NonNull Request request) {
    targetTracker.track(target);
	// Execute the network request
    requestTracker.runRequest(request);
  }
Copy the code

RequestTracker#runRequest

  public void runRequest(@NonNull Request request) {
    // Manage requests by adding each submitted request to a set
    requests.add(request);
	// Check whether Glide is currently paused
    if(! isPaused) {// If not paused, the begin method of SingleRequest is called to execute the request
      request.begin();
    } else {
      request.clear();
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Paused, delaying request");
      }
	  // If the request is paused, the current request is added to the queue to wait for the suspension to be completedpendingRequests.add(request); }}Copy the code

Before loading the picture, that is, before starting the network request, we need to add each request to the set to manage the request, and also need to judge the current state of Glide, because we are now analyzing the picture load process, obviously this is not a suspended state, so we will execute the begin method of request. Since we have analyzed the network request object as SingleRequest above, the request here is the SingleRequest object.

2.4.2 loads

Next, let’s look at the begin method of SingleRequest

SingleRequest#begin

  public synchronized void begin(a) {...//model is the image URL passed to load
    if (model == null) {
      if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        width = overrideWidth;
        height = overrideHeight;
      }
       
      int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
	  // If the incoming URL address is empty, onLoadFailed is called
      onLoadFailed(new GlideException("Received null model"), logLevel);
      return;
    }

    status = Status.WAITING_FOR_SIZE;
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
	  // Focus on
      onSizeReady(overrideWidth, overrideHeight);
    } else {
    &emsp;// getSize calculates the width and height and executes the onSizeReady method
      target.getSize(this);
    }

    if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
        && canNotifyStatusChanged()) {
      Loading placeholder is used to replace the final image display before the image request is successfultarget.onLoadStarted(getPlaceholderDrawable()); }... }Copy the code
1. onLoadFailed

We can see from the above code that the onLoadFailed method is called when model is null, i.e. the image address passed in by load is empty

SingleRequest#onLoadFailed

  private synchronized void onLoadFailed(GlideException e, int maxLogLevel) {... loadStatus =null;
    status = Status.FAILED;

    isCallingCallbacks = true;
    try {
      //TODO: what if this is a thumbnail request?
      boolean anyListenerHandledUpdatingTarget = false;
      if(requestListeners ! =null) {
        for(RequestListener<R> listener : requestListeners) { anyListenerHandledUpdatingTarget |= listener.onLoadFailed(e, model, target, isFirstReadyResource());  } } anyListenerHandledUpdatingTarget |= targetListener ! =null
              && targetListener.onLoadFailed(e, model, target, isFirstReadyResource());

      if(! anyListenerHandledUpdatingTarget) {// Focus on this methodsetErrorPlaceholder(); }}finally {
      isCallingCallbacks = false;
    }

    notifyLoadFailed();
  }


  private synchronized void setErrorPlaceholder(a) {
    if(! canNotifyStatusChanged()) {return;
    }

    Drawable error = null;
	// Get the image of fallback
    if (model == null) {
      error = getFallbackDrawable();
    }
    // If the fallback graph is not set, the error graph is obtained
    if (error == null) {
      error = getErrorDrawable();
    }
    // If there is no error graph, get another loading placeholder map
    if (error == null) {
      error = getPlaceholderDrawable();
    }
    / / target for DrawableImageViewTarget
    target.onLoadFailed(error);
  }
Copy the code

In the onLoadFailed method we just need to focus on the setErrorPlaceholder method, and the main logic in setErrorPlaceholder is to get the picture that you need to show when you get an error, Press Fallback > Error > Loading’s priority to get the picture at the time of the error and then call the onLoadFailed method of DrawableImageViewTarget. By looking at the DrawableImageViewTarget, we can see that there is no onLoadFailed method in this class, so we naturally look for the parent class ImageViewTarget to see if this method exists.

ImageViewTarget#onloadFailed

  public void onLoadFailed(@Nullable Drawable errorDrawable) {
    super.onLoadFailed(errorDrawable);
    setResourceInternal(null);
	// Call setDrawable to display the image
    setDrawable(errorDrawable);
  }

  public void setDrawable(Drawable drawable) {
    // View is an ImageView that displays the image
    view.setImageDrawable(drawable);
  }

Copy the code

It can be seen that the principle of Glide display wrong picture is: when the url of incoming picture is null, placeholder map of fallback/error/loading will be used instead.

2. onLoadStarted

After analyzing onLoadFailed, we go back to SingleRequest’s begin method. It should be onSizeReady next, but since this method is complicated and onLoadStarted is similar to onLoadStarted, So let’s analyze onLoadStarted and leave onSizeReady to the end.

SingleRequest#begin code related to onLoadStarted

    if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
        && canNotifyStatusChanged()) {
      Loading placeholder is used to replace the final image display before the image request is successful
      target.onLoadStarted(getPlaceholderDrawable());
    }
Copy the code

The onLoadStarted method of DrawableImageViewTarget is executed while the image is being requested or waiting for the onSizeReady method to be executed. The onLoadFailed method is in the DrawableImageViewTarget parent class ImageViewTarget, so onLoadStarted is in the ImageViewTarget parent class.

ImageViewTarget#onLoadStarted

  public void onLoadStarted(@Nullable Drawable placeholder) {
    super.onLoadStarted(placeholder);
    setResourceInternal(null);
	// Loading placeholder is used to replace the final image display before the image request starts
    setDrawable(placeholder);
  }

  public void setDrawable(Drawable drawable) {
    // Display the image
    view.setImageDrawable(drawable);
  }
Copy the code

Then the loading placeholder is displayed, that is, the loading placeholder is used to replace the final image display before the image request is successful. This is a feature we use a lot.

3. onSizeReady

Now that we’re ready to analyze onSizeReady, let’s post the code

SingleRequest#begin code related to onSizeReady

	// There are two ways to load images:
	Override () API is used to specify a fixed width and height for the image
	/ / 2. No use
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
		// In the first case, onSizeReady is called if the width and height are specified
      onSizeReady(overrideWidth, overrideHeight);
    } else {
    &emsp;// getSize calculates the width and height and executes the onSizeReady method
      //(Trace up from DrawableImageViewTarget and find this method in ViewTarget)
      target.getSize(this);
    }
Copy the code

We’ll just analyze onSizeReady for this, because the getSize method will eventually call onSizeReady as well.

SingleRequest#onSizeReady

  @Override
  public synchronized void onSizeReady(int width, int height) {... status = Status.RUNNING; loadStatus =// Focus on calling Engine's load build task
    	// Focus on the penultimate argument, passing itself SingleRequest, which will be used during the callback
    	// Focus on the penultimate argument, passing the thread pool callbackExectuter with the Handler bound to the main thread
        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,
            callbackExecutor);
  }
Copy the code

The onSizeReady implementation is passed to the load method of Engine, which is the execution Engine used by Glide in The first track with. The last two parameters passed to load need special attention because they will be used later in the analysis.

Build tasks

Engine#load

  public synchronized <R> LoadStatus load(
      GlideContext glideContext,
      Object model,
      Key signature,
      int width,
      intheight, 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,
      Executor callbackExecutor) {...// Find the task corresponding to the key from the cacheEngineJob<? > current = jobs.get(key, onlyRetrieveFromCache);if(current ! =null) {
	  // If you go to this point, the task is already being executed, no need to build again
	  // You can go back and look at this after analyzing it from the back
      current.addCallback(cb, callbackExecutor);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Added to existing load", startTime, key);
      }
      return new LoadStatus(cb, current);
    }
	// This is a new assignment
    // Create EngineJob object to start the thread (asynchronously loading images)
    EngineJob<R> engineJob =
        engineJobFactory.build(
            key,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache);
    // Create DecodeJob object to decode images
    DecodeJob<R> decodeJob =
        decodeJobFactory.build(
            glideContext,
            model,
            key,
            signature,
            width,
            height,
            resourceClass,
            transcodeClass,
            priority,
            diskCacheStrategy,
            transformations,
            isTransformationRequired,
            isScaleOnlyOrNoTransform,
            onlyRetrieveFromCache,
            options,
            engineJob);
    // Add to the task cache
    jobs.put(key, engineJob);
    // For now
    // This method is reanalyzed when the data is retrieved for the photo display callback
    engineJob.addCallback(cb, callbackExecutor);
	// Execute the taskengineJob.start(decodeJob); .return new LoadStatus(cb, engineJob);
  }
Copy the code

Combined with the above code and comments, we can see what Engine.Load does:

  • Create EngineJob object to start the thread
  • Create a DeodeJob object that decodes the photo
  • Add callback objects and thread pools
  • Perform a task
Perform a task

EngineJob#start

  public synchronized void start(DecodeJob<R> decodeJob) {
    this.decodeJob = decodeJob;
	// Get the thread pool
    GlideExecutor executor = decodeJob.willDecodeFromCache()
        ? diskCacheExecutor
        : getActiveSourceExecutor();
	// Execute DecodeJob's run method
    executor.execute(decodeJob);
  }
Copy the code

The execute method of the thread pool is called, so the run method of the DecodeJob is executed next

DecodeJob#run

  public void run(a) {...try {
      if (isCancelled) {
        notifyFailed();
        return;
      }
	  // Focus, call runWrappedrunWrapped(); }... }private void runWrapped(a) {
    switch (runReason) {
      case INITIALIZE:
	  	// Get the task scenario
        stage = getNextStage(Stage.INITIALIZE);
	    // Get the executor of the scene
        currentGenerator = getNextGenerator();
	    // The executor performs the task
        runGenerators();
        break;
      case SWITCH_TO_SOURCE_SERVICE:
        runGenerators();
        break;
      case DECODE_DATA:
        decodeFromRetrievedData();
        break;
      default:
        throw new IllegalStateException("Unrecognized run reason: "+ runReason); }}// Get the task scenario
  private Stage getNextStage(Stage current) {
    switch (current) {
      case INITIALIZE:
	  	Return stage.resource_cache if the configured cache policy allows reading from the resource cache
        return diskCacheStrategy.decodeCachedResource()
            ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
      case RESOURCE_CACHE:
	  	Stage.data_cache is returned if the configured cache policy allows reading from the source data cache
        return diskCacheStrategy.decodeCachedData()
            ? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
      case DATA_CACHE:
        // If only data can be read from the cache, FINISH, otherwise return stage.source to load the new resource
        return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
      case SOURCE:
      case FINISHED:
        return Stage.FINISHED;
      default:
        throw new IllegalArgumentException("Unrecognized stage: "+ current); }}// Get the executor of the scene
  private DataFetcherGenerator getNextGenerator(a) {
    switch (stage) {
      case RESOURCE_CACHE:
	  	// Executor of the resource disk cache
        return new ResourceCacheGenerator(decodeHelper, this);
      case DATA_CACHE:
	  	// Executor of the source data disk cache
        return new DataCacheGenerator(decodeHelper, this);
      case SOURCE:
	  	// No cache, the executor of the source of the data
        return new SourceGenerator(decodeHelper, this);
      case FINISHED:
        return null;
      default:
        throw new IllegalStateException("Unrecognized stage: "+ stage); }}private void runGenerators(a) {
    currentThread = Thread.currentThread();
    startFetchTime = LogTime.getLogTime();
    boolean isStarted = false;
	/ / call DataFetcherGenerator. StartNext () to perform the requested action
	// DataFetcherGenerator should be SourceGenerator
    while(! isCancelled && currentGenerator ! =null
        && !(isStarted = currentGenerator.startNext())) {
      stage = getNextStage(stage);
      currentGenerator = getNextGenerator();

      if (stage == Stage.SOURCE) {
        reschedule();
        return; }}... }Copy the code

How to carry out the mission? Basically, it can be divided into three steps:

  1. Obtaining the Task Scenario
  2. Gets the actor of the task scenario
  3. The executor performs the task

There is a one-to-one mapping between the scenario and the executor. Since we are analyzing the first loading image and no caching policy is configured, the corresponding task scenario is no caching, and the corresponding executor is the SourceGenerator object. So the startNext method of SourceGenerator is called when the task is executed

SourceGenerator#startNext

  public boolean startNext(a) {...boolean started = false;
    while(! started && hasNextModelLoader()) {// Get a data loader from DecodeHelper's data loader collection
      loadData = helper.getLoadData().get(loadDataListIndex++);
      if(loadData ! =null
          && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
          || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
        started = true;

		// Use the fetcher loader to perform data loading
		// This fetcher is an HttpUrlFetcher object
        loadData.fetcher.loadData(helper.getPriority(), this); }}return started;
  }
Copy the code
Get the data loader

How do you get a collection of data loaders

DecodeHelper#getLoadData

List<LoadData<? >> getLoadData() {if(! isLoadDataSet) { isLoadDataSet =true;
      loadData.clear();
	  // Get modelLoaders from the Glide registerList<ModelLoader<Object, ? >> modelLoaders = glideContext.getRegistry().getModelLoaders(model);/ / traverse modelLoaders
      for (int i = 0, size = modelLoaders.size(); i < size; i++) {
        // One of the implementation classes is StringLoaderModelLoader<Object, ? > modelLoader = modelLoaders.get(i);LoadData is constructed with StringLoader
		// The HttpGlideUrlLoader buildLoadData method is finally executed after Glide registry analysis
		// The final loadData encapsulates the HttpUrlFetcher objectLoadData<? > current = modelLoader.buildLoadData(model, width, height, options);if(current ! =null) {
		  // Add to the loadData collectionloadData.add(current); }}}// Returns a loadData collection containing HttpUrlFetcher objects
    return loadData;
  }
Copy the code

Let’s break down the method step by step, first getting the modelLoaders from Glide Registered Registry, and since we use String as our entire example, the model here will be of type String.

!!!!!!!!! Note: The static factory class of ModelLoaderFactory implemented by ModelLoader is registered in the Registry. When the getModelLoaders of Registry is called, the build method in the factory class will be called. The process will not be posted here. Now we just need to know that the build method of the corresponding factory class in the registry is called when the getModelLoaders method is called. Now we need to go back to the Glide build registry and look at the static factory classes for ModelLoader when Model was String. Here are just a few:

Glide# Glide constructor

 registry
		/ / focus on StringLoader. StreamFactory ()
        .append(String.class, InputStream.class, new StringLoader.StreamFactory())
        .append(String.class, ParcelFileDescriptor.class, new StringLoader.FileDescriptorFactory())
        .append(
            String.class, AssetFileDescriptor.class, new StringLoader.AssetFileDescriptorFactory())
Copy the code

. Here we use StringLoader StreamFactory for example, by calling getModelLoaders method, so does StringLoader. StreamFactory build method

StringLoader.StreamFactory()

  public static class StreamFactory implements ModelLoaderFactory<String.InputStream> {

    @NonNull
    @Override
    public ModelLoader<String, InputStream> build( @NonNull MultiModelLoaderFactory multiFactory) {
      // It is known from the Models registry of Glide's Registry
      // Httpuriloader.factory ()
      // The final argument returns an HttpGlideUrlLoader object
      return newStringLoader<>(multiFactory.build(Uri.class, InputStream.class)); }... }Copy the code

From the build method, we build the StringLoader object, but the argument calls another MultiModelLoaderFactory, so we need to look at the Glide register, Then find the MultiModelLoaderFactory object built with uri. class, inputStream. class

Glide#Glide constructor

  registry       
		// Focus on httpuriloader.factory ()
        .append(Uri.class, InputStream.class, new HttpUriLoader.Factory())
Copy the code

The MultiModelLoaderFactory object will be of type Httpuriloader.factory (), so we need to look at the build method as well

HttpUriLoader.Factory#build

    public ModelLoader<Uri, InputStream> build(MultiModelLoaderFactory multiFactory) {
      // See the Models registry in Glide Registry
      / / this time multiFactory for HttpGlideUrlLoader. Factory ()
      return new HttpUriLoader(multiFactory.build(GlideUrl.class, InputStream.class));
    }
Copy the code

Continue with the same steps as above. Go to Glide’s registry and find the MultiModelLoaderFactory object with glideurl.class, inputStream.class

     registry    
		// Focus on HttpGlideUrlLoader
        .append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
Copy the code

Look again at HttpGlideUrlLoader. The build method of the Factory

HttpGlideUrlLoader.Factory#build

    public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {
      // The HttpGlideUrlLoader object is returned
      return new HttpGlideUrlLoader(modelCache);
    }
Copy the code

The build method here returns the HttpGlideUrlLoader type, so the parameters in the StringLoader object will eventually be of the HttpGlideUrlLoader type. So let’s look at the implementation of the StringLoader constructor.

StringLoader# StringLoader constructor

  public StringLoader(ModelLoader<Uri, Data> uriLoader) {
    // The uriLoader is an HttpGlideUrlLoader object assigned to a static member variable
    this.uriLoader = uriLoader;
  }
Copy the code

The builder simply assigns a value to a member variable, in which case the uriLoader is the HttpGlideUrlLoader object. This is what getModelLoaders does. We continue to analyze the getLoadData method of DecodeHelper, which iterates through each modelLoader when it gets a String of modelLoaders. We then call modelLoader’s buildLoadData to construct a loadData object. Here we directly use StringLoader as an example. Let’s look at the implementation of buildLoadData for StringLoader

StringLoader#buildLoadData

  public LoadData<Data> buildLoadData(@NonNull String model, int width, int height,
      @NonNull Options options) {
    Uri uri = parseUri(model);
    if (uri == null| |! uriLoader.handles(uri)) {return null;
    }
	// uriLoader is the HttpGlideUrlLoader object
    return uriLoader.buildLoadData(uri, width, height, options);
  }
Copy the code

HttpGlideUrlLoader: HttpGlideUrlLoader: HttpGlideUrlLoader: HttpGlideUrlLoader: HttpGlideUrlLoader: HttpGlideUrlLoader

HttpGlideUrlLoader

  public LoadData<InputStream> buildLoadData(@NonNull GlideUrl model, int width, int height,
      @NonNull Options options) {
    // GlideUrls memoize parsed URLs so caching them saves a few object instantiations and time
    // spent parsing urls.
    GlideUrl url = model;
    if(modelCache ! =null) {
      url = modelCache.get(model, 0.0);
      if (url == null) {
        modelCache.put(model, 0.0, model); url = model; }}int timeout = options.get(TIMEOUT);
	// Creates a LoadData object and encapsulates an HttpUrlFetcher object
    return new LoadData<>(url, new HttpUrlFetcher(url, timeout));
  }
Copy the code

We don’t need to pay too much attention to the rest of the code. We just need to pay attention to the return value. We can see that the LoadData object that is returned is the LoadData object that encapsulates HttpUrlFetcher. Let’s return to the startNext method of SourceGenerator.

SourceGenerator#startNext

  public boolean startNext(a) {...boolean started = false;
    while(! started && hasNextModelLoader()) {// The final object is the LoadData object that encapsulates HttpUrlFetcher
      loadData = helper.getLoadData().get(loadDataListIndex++);
      if(loadData ! =null
          && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
          || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
        started = true;

		// Use the fetcher loader to perform data loading
		// This fetcher is an HttpUrlFetcher object
        loadData.fetcher.loadData(helper.getPriority(), this); }}return started;
  }
Copy the code

LoadData is a loadData object that encapsulates HttpUrlFetcher, so loading data is actually calling the loadData method of HttpUrlFetcher.

Perform data loading

HttpUrlFetcher#loadData

  public void loadData(@NonNull Priority priority,
      @NonNull DataCallback<? super InputStream> callback) {
    long startTime = LogTime.getLogTime();
    try {
	  // Get the input stream of the network image
      InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0.null, glideUrl.getHeaders());
	  // Call inputStream back, callback to DataCallbackcallback.onDataReady(result); }... }// Network request code, using HttpURLConnection for network request
  private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl,
      Map<String, String> headers) throws IOException {...// Static factory creates an HttpUrlConnection object
    urlConnection = connectionFactory.build(url);
    for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
      urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
    }
	// Set the connection timeout to 2500ms
    urlConnection.setConnectTimeout(timeout);
	// Set the read timeout to 2500ms
    urlConnection.setReadTimeout(timeout);
	// Do not use HTTP caching
    urlConnection.setUseCaches(false);
    urlConnection.setDoInput(true);

    // Stop the urlConnection instance of HttpUrlConnection from following redirects so that
    // redirects will be handled by recursive calls to this method, loadDataWithRedirects.
    urlConnection.setInstanceFollowRedirects(false);

    // Connect explicitly to avoid errors in decoders if connection fails.
    urlConnection.connect();
    // Set the stream so that it's closed in cleanup to avoid resource leaks. See #2352.
    stream = urlConnection.getInputStream();
    if (isCancelled) {
      return null;
    }
    final int statusCode = urlConnection.getResponseCode();
    if (isHttpOk(statusCode)) {
	  // The request succeeded
      returngetStreamForSuccessfulRequest(urlConnection); }... }private InputStream getStreamForSuccessfulRequest(HttpURLConnection urlConnection)
      throws IOException {
    if (TextUtils.isEmpty(urlConnection.getContentEncoding())) {
      int contentLength = urlConnection.getContentLength();
      stream = ContentLengthInputStream.obtain(urlConnection.getInputStream(), contentLength);
    } else {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Got non empty content encoding: " + urlConnection.getContentEncoding());
      }
      stream = urlConnection.getInputStream();
    }
	// Finally returns an InputStream of the image, and has not yet started reading data
    return stream;
  }
Copy the code

As you can see, there are two tasks to perform data loading. The first one is to fetch the InputStream of the data, which takes an HttpURLConnection for the network request. The last one is to fetch the InputStream object of the data.

Return the data

Once you get the input stream, you need to return the input stream. How do you return it?

callback.onDataReady(result);
Copy the code

You can see that a callback method is used to call back and forth the input stream of data. Callbak is a DataCallback object. The next step is to find a class that implements the DataCallback interface. The loadData method is called by the startNext method in SourceGenerator, so our preferred target is the SourceGenerator class

SourceGenerator#onDataReady

class SourceGenerator implements DataFetcherGenerator.DataFetcher.DataCallback<Object>,
    DataFetcherGenerator.FetcherReadyCallback {...public void onDataReady(Object data) {
    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
    if(data ! =null&& diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) { dataToCache = data; . }else {
	  // Call the onDataFetcherReady method of FetcherReadyCallback to call back datacb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher, loadData.fetcher.getDataSource(), originalKey); }}}Copy the code

Resourceful as we are! The SourceGenerator class implements DataFetcher.DataCallback, and the onDataReady method is found in the SourceGenerator class. The onDataFetcherReady method of FetcherReadyCallback is called, so we look back and ask ourselves: in which class did SourceGenerator’s startNext method get called? You will see that the startNext method is called in the run method of the DecodeJob, and immediately see if the DecodeJob implements onDataFetcherReady!

DecodeJob#onDataFetcherReady

  
class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback.Runnable.Comparable<DecodeJob<? > >,Poolable {...public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher
        fetcher, DataSource dataSource, Key attemptedKey) {...if(Thread.currentThread() ! = currentThread) { runReason = RunReason.DECODE_DATA; callback.reschedule(this);
    } else {
      GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData");
      try {
	  	// Parse the obtained data
        decodeFromRetrievedData();
      } finally{ GlideTrace.endSection(); }}}private void decodeFromRetrievedData(a) {...try {
	  // Get the parse
      resource = decodeFromData(currentFetcher, currentData, currentDataSource);
    } catch (GlideException e) {
      e.setLoggingDetails(currentAttemptingKey, currentDataSource);
      throwables.add(e);
    }
    if(resource ! =null) {
	  // Notifies the external world that the resource is successfully obtained
      notifyEncodeAndRelease(resource, currentDataSource);
    } else{ runGenerators(); }}}Copy the code

A: wow! Super god! Sure enough! The onDataFetcherReady method does two main things:

  1. Parse the obtained data
  2. Back to image resources

Let’s see how we parse the data first, okay

2.5 Parsing Data

DecodeJob

  private <Data> Resource<R> decodeFromData(DataFetcher
        fetcher, Data data, DataSource dataSource) throws GlideException {
    try{...// Focus on decodeFromFetcher methods
      Resource<R> result = decodeFromFetcher(data, dataSource);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Decoded result " + result, startTime);
      }
      returnresult; }... }private <Data> Resource<R> decodeFromFetcher(Data data, DataSource dataSource)
      throws GlideException {
    // Get the parser LoadPath for the current data class, where data is an InputStream objectLoadPath<Data, ? , R> path = decodeHelper.getLoadPath((Class<Data>) data.getClass());// Data is parsed through a parser
    return runLoadPath(data, dataSource, path);
  }

  private <Data, ResourceType> Resource<R> runLoadPath(Data data, DataSource dataSource, LoadPath
       
         path)
       ,> throws GlideException {
    Options options = getOptionsWithHardwareConfig(dataSource);
	// Data is InputStream, rewinder is InputStreamRewinder
    DataRewinder<Data> rewinder = glideContext.getRegistry().getRewinder(data);
    try {
      // Move data parsing to the loadpath. load method
      return path.load(
          rewinder, options, width, height, new DecodeCallback<ResourceType>(dataSource));
    } finally{ rewinder.cleanup(); }}Copy the code

Retrieving the Rewinder, like retrieving the modelLoaders, requires revisiting the Glide build registry, which is not detailed here because data is an InputStream object. So rewinder is an InputStreamRewinder object, and then the load method of LoadPath is called to implement parsing the data

LoadPath

  public Resource<Transcode> load(DataRewinder<Data> rewinder, @NonNull Options options, int width,
      int height, DecodePath.DecodeCallback<ResourceType> decodeCallback) throws GlideException {
    List<Throwable> throwables = Preconditions.checkNotNull(listPool.acquire());
    try {
	  // Focus on
      return loadWithExceptionList(rewinder, options, width, height, decodeCallback, throwables);
    } finally{ listPool.release(throwables); }}private Resource<Transcode> loadWithExceptionList(DataRewinder<Data> rewinder,
      @NonNull Options options,
      int width, int height, DecodePath.DecodeCallback<ResourceType> decodeCallback,
      List<Throwable> exceptions) throws GlideException {
    Resource<Transcode> result = null;
    // Walk through the DecodePath collection
    for (int i = 0, size = decodePaths.size(); i < size; i++) {
      DecodePath<Data, ResourceType, Transcode> path = decodePaths.get(i);
      try {
	  	// call DecodePath. Decode to actually parse the data
        result = path.decode(rewinder, width, height, options, decodeCallback);
      } catch (GlideException e) {
        exceptions.add(e);
      }
      if(result ! =null) {
        break; }}if (result == null) {
      throw new GlideException(failureMessage, new ArrayList<>(exceptions));
    }

    return result;
  }

Copy the code

DecodePath

  public Resource<Transcode> decode(DataRewinder<DataType> rewinder, int width, int height,
      @NonNull Options options, DecodeCallback<ResourceType> callback) throws GlideException {
    // Get the Resource
      
        object
      
    Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options);
	// Convert resources to target effects, such as CenterCrop set when building request
    Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);
	/ / target format, data can be converted to the Resource < Bitmap > convert LazyBitmapDrawableResource object
	/ / by LazyBitmapDrawableResource get access to a BitmapDrawable object
	// The transcoder is BitmapDrawableTranscoder
    return transcoder.transcode(transformed, options);
  }
Copy the code

LoadPath’s load method will eventually parse the data by calling DecodePath’s decode, whose main job is to fetch Resource objects, Then the Resource object into LazyBitmapDrawableResource. Considering the length problem, we will not analyze how to get the Resource object here, but only how to convert the data into the target format. We can find the BitmapDrawable transcoder in the Glide construction registry. So we’re actually calling BitmapDrawableTranscoder’s Transcode to do the conversion

BitmapDrawableTranscoder#transcode

  public Resource<BitmapDrawable> transcode(@NonNull Resource
       
         toTranscode, @NonNull Options options)
        {
    / / get LazyBitmapDrawableResource object
    return LazyBitmapDrawableResource.obtain(resources, toTranscode);
  }
Copy the code

LazyBitmapDrawableResource

  public static Resource<BitmapDrawable> obtain( @NonNull Resources resources, @Nullable Resource
       
         bitmapResource)
        {
    if (bitmapResource == null) {
      return null;
    }
	/ / create a LazyBitmapDrawableResource object
    return new LazyBitmapDrawableResource(resources, bitmapResource);
  }
  
  public BitmapDrawable get(a) {
    // Return a BitmapDrawable
    return new BitmapDrawable(resources, bitmapResource.get());
  }
Copy the code

Tracking down can find transcode will eventually get a encapsulates the Resource object, then watch LazyBitmapDrawableResource the get method, can get a BitmapDrawable object, namely target format. Here is the data successfully parsed into LazyBitmapDrawableResource object.

2.6 Display images in the main thread

Now that the data is parsed, all that remains is to display the data, so we have to go back to the decodeFromRetrievedData method of the DecodeJob

DecodeJob

   private void decodeFromRetrievedData(a) {...try {
	  After the success of the / / parse the resource to encapsulate the resource < Bitmap > LazyBitmapDrawableResource object
      // The BitmapDrawable object can be obtained by the get method
      resource = decodeFromData(currentFetcher, currentData, currentDataSource);
    } catch (GlideException e) {
      e.setLoggingDetails(currentAttemptingKey, currentDataSource);
      throwables.add(e);
    }
    if(resource ! =null) {
	  // Notifies the external world that the resource is successfully obtained
      notifyEncodeAndRelease(resource, currentDataSource);
    } else{ runGenerators(); }}private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {...// Focus onnotifyComplete(result, dataSource); . }private void notifyComplete(Resource<R> resource, DataSource dataSource) {
    setNotifiedOrThrow();
	// callback to Engine job (DecodeJob)
    callback.onResourceReady(resource, dataSource);
  }
Copy the code

2.6.1 Callback data

Callback () callback (); callback (); callback (); Don’t worry, let’s break it down step by step.

First make sure that notifyComplete is in the DecodeJob class, so callback should be a member variable, and then figure out where to assign the value

  // Focus on the penultimate argument, callback of type callback
  DecodeJob<R> init(
      GlideContext glideContext,
      Object model,
      EngineKey loadKey,
      Key signature,
      int width,
      intheight, Class<? > resourceClass, Class<R> transcodeClass, Priority priority, DiskCacheStrategy diskCacheStrategy, Map<Class<? >, Transformation<? >> transformations,boolean isTransformationRequired,
      boolean isScaleOnlyOrNoTransform,
      boolean onlyRetrieveFromCache,
      Options options,
      Callback<R> callback,
      int order) {
    decodeHelper.init(
        glideContext,
        model,
        signature,
        width,
        height,
        diskCacheStrategy,
        resourceClass,
        transcodeClass,
        priority,
        options,
        transformations,
        isTransformationRequired,
        isScaleOnlyOrNoTransform,
        diskCacheProvider);
    this.glideContext = glideContext;
    this.signature = signature;
    this.priority = priority;
    this.loadKey = loadKey;
    this.width = width;
    this.height = height;
    this.diskCacheStrategy = diskCacheStrategy;
    this.onlyRetrieveFromCache = onlyRetrieveFromCache;
    this.options = options;
    this.callback = callback;
    this.order = order;
    this.runReason = RunReason.INITIALIZE;
    this.model = model;
    return this;
  }
Copy the code

It is easy to see that the init method assigns a value to the callback, so remember that the position of the callback argument is the second to last. Now you’re thinking: where is the init method of DecodeJob called? Then try to figure out: since it is an assignment estimate will be called when building DecodeJob. The question then becomes: where is DecodeJob built above? Then silently: DecodeJob is used to execute tasks, so it should be called when building tasks! (Most of the time, though, my mind goes blank and I can’t think of anything, and I can’t think of anything here. You can then go directly to where DecodeJob first appeared) and eventually find the DecodeJob build in Engine load

Engine#load

public synchronized <R> LoadStatus load(...).{
    // Focus on the last to last parameter
	DecodeJob<R> decodeJob =
        decodeJobFactory.build(
            glideContext,
            model,
            key,
            signature,
            width,
            height,
            resourceClass,
            transcodeClass,
            priority,
            diskCacheStrategy,
            transformations,
            isTransformationRequired,
            isScaleOnlyOrNoTransform,
            onlyRetrieveFromCache,
            options,
            engineJob);

}
    // Focus on the last parameter
    <R> DecodeJob<R> build(GlideContext glideContext,
        Object model,
        EngineKey loadKey,
        Key signature,
        int width,
        intheight, Class<? > resourceClass, Class<R> transcodeClass, Priority priority, DiskCacheStrategy diskCacheStrategy, Map<Class<? >, Transformation<? >> transformations,boolean isTransformationRequired,
        boolean isScaleOnlyOrNoTransform,
        boolean onlyRetrieveFromCache,
        Options options,
        DecodeJob.Callback<R> callback) {
      DecodeJob<R> result = Preconditions.checkNotNull((DecodeJob<R>) pool.acquire());
      returnresult.init( glideContext, model, loadKey, signature, width, height, resourceClass, transcodeClass, priority, diskCacheStrategy, transformations, isTransformationRequired, isScaleOnlyOrNoTransform, onlyRetrieveFromCache, options, callback, creationOrder++); }}Copy the code

In the code above the first call DecodeJobFactory DecodeJob, build method to construct the DecodeJobFactory is Engine inner class, then see DecodeJobFactory build method, wow! It’s exactly what we thought! Build call DecodeJob init method, do not forget our task is what! Find the value of callback, go back to the build callback argument, and then go back to the load of Engine to call the last build argument! EngineJob! Callback (DecodeJob) : DecodeJob (callback) : DecodeJob (callback) : DecodeJob (callback) : DecodeJob (callback) : DecodeJob (callback) So the onResourceReady method of the EngineJob is called back

EngineJob#onResourceReady

  public void onResourceReady(Resource<R> resource, DataSource dataSource) {
    synchronized (this) {
      this.resource = resource;
      this.dataSource = dataSource;
    }
	// Focus on
    notifyCallbacksOfResult();
  }

  void notifyCallbacksOfResult(a) { ResourceCallbacksAndExecutors copy; Key localKey; EngineResource<? > localResource;synchronized (this) {...// Focus on the type of CBS
	  // Find the type in CBScopy = cbs.copy(); . }// Notify the upper-layer Engine that the task is complete
    listener.onEngineJobComplete(this, localKey, localResource);

    for (final ResourceCallbackAndExecutor entry : copy) {
	  // Call back to ImageViewTarget to display the resource
      entry.executor.execute(new CallResourceReady(entry.cb));
    }
    decrementPendingCallbacks();
  }
Copy the code

2.6.2 Returning to the main thread

It’s time to confirm the parameter type. Call Holmes online! Let’s start by identifying the most important piece of code in the onResourceReady method of EngineJob

	for (final ResourceCallbackAndExecutor entry : copy) {
	  // Call back to ImageViewTarget to display the resource
      entry.executor.execute(new CallResourceReady(entry.cb));
    }
Copy the code

Before we can determine the execute method for the thread pool, we need to do the following:

  • Determine the entry. Executor type
  • Determine the entry. Cb type

Now we know that entry for ResourceCallbackAndExecutor method, so let’s take a look at the class and constructor

ResourceCallbackAndExecutor

  static final class ResourceCallbackAndExecutor {
    final ResourceCallback cb;
    final Executor executor;

    ResourceCallbackAndExecutor(ResourceCallback cb, Executor executor) {
      this.cb = cb;
      this.executor = executor; }}Copy the code

Can be found in the executor and cb are ResourceCallbackAndExecutor member variables, assigned during construction, so we need to find a place to construct ResourceCallbackAndExecutor object, Naturally we lock the copy variable up there

EngineJob

 
final ResourceCallbacksAndExecutors cbs = new ResourceCallbacksAndExecutors();

void notifyCallbacksOfResult(a) { ResourceCallbacksAndExecutors copy; Key localKey; EngineResource<? > localResource;synchronized (this) {...// Focus on the type of CBS
	  // Find the type in CBScopy = cbs.copy(); . }... }ResourceCallbacksAndExecutors copy(a) {
      return new ResourceCallbacksAndExecutors(new ArrayList<>(callbacksAndExecutors));
    }

     
    // where CBS is assigned
    synchronized void addCallback(final ResourceCallback cb, Executor callbackExecutor) {
    stateVerifier.throwIfRecycled();
	// The cb type is singleRequest, which implements ResourceCallback interface
	//callbackExecutor is a thread pool bound to the main thread Handler
	/ / type for ResourceCallbacksAndExecutors CBS
	/ / add the internal implementation is to create ResourceCallbacksAndExecutor and cb, callbackExecutor assignment to its member variables
    // Then add it to CBScbs.add(cb, callbackExecutor); . }void add(ResourceCallback cb, Executor executor) {
      callbacksAndExecutors.add(new ResourceCallbackAndExecutor(cb, executor));
    }

Copy the code

Call, let’s take a look at the copy assignment is called ResourceCallbacksAndExecutors copy method types of CBS, the copy is created ResourceCallbacksAndExecutor collection, the collection is in fact CBS, We also need to find the CBS assignment, and after a while you’ll find the CBS Add method in the addCallback method, The add method of the internal implementation is actually create ResourceCallbacksAndExecutor and cb, callbackExecutor assignment to its member variables, so we have to determine what is the add method of two parameters? If you remember, the addCallback method was specifically mentioned during the build task, so let’s revisit Engine’s load method.

Engine#load

.// Call addCallback() to register a ResourceCallback
	// where cb is the penultimate argument to load, which is called in singleRequest onSizeReady()
	// The cb type is singleRequest
	// Go back to the EngineJob addCallback method
    engineJob.addCallback(cb, callbackExecutor);
	// Execute DecodeJob's run in the child thread
    engineJob.start(decodeJob);
Copy the code

To determine the types of CB and callbackExecutor, we need to step back

  // Pay special attention to the last two parameters
  public synchronized <R> LoadStatus load(
      GlideContext glideContext,
      Object model,
      Key signature,
      int width,
      intheight, 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,
      Executor callbackExecutor)
Copy the code

SingleRequest#onSizeReady

    loadStatus =
    	// Focus on the penultimate parameter, passing in this, the SingleRequest object that implements the ResourceCallback interface
    	// Focus on the penultimate argument, passing the r thread pool callbackExectuter with the Handle bound to the main thread
        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,
            callbackExecutor);
Copy the code

In SingleRequest onSizeReady, we confirm that cb is the SingleRequest object. For another parameter, we will not post the code one by one because of the space. You can directly look back from the onSizeReady method. As mentioned in the comments above, you will eventually see that the callbackExecutor is actually the pool of threads that we mentioned earlier that contains the bound main thread Handler. Let’s go back to where we started

EngineJob#onResourceReady

	for (final ResourceCallbackAndExecutor entry : copy) {
	  // Call back to ImageViewTarget to display the resource
       //entry.cb indicates the singleRequest type
	   //entry.executor is a thread pool containing MAIN_THREAD_EXECUTOR handlers bound to the main thread
      entry.executor.execute(new CallResourceReady(entry.cb));
    }
Copy the code

So take a look at Executors’ mainThreadExecutor method (rewatch 2.2 above if you forgot)

private static final Executor MAIN_THREAD_EXECUTOR =
      new Executor() {
       // Bind the main thread Looper
        private final Handler handler = new Handler(Looper.getMainLooper());

        @Override
        public void execute(@NonNull Runnable command) { handler.post(command); }};public static Executor mainThreadExecutor(a) {
    return MAIN_THREAD_EXECUTOR;
  }
Copy the code

After the execute method of MAIN_THREAD_EXECUTOR is called, the run method of the CallResourceReady object is executed in the main thread. So let’s look at the Run method of CallResourceReady

2.6.3 Displaying pictures

EngineJob.CallResourceReady#run

    public void run(a) {
      synchronized (EngineJob.this) {
        if (cbs.contains(cb)) {
          // Acquire for this particular callback.
          engineResource.acquire();
		  // The cb is SingleRequestcallCallbackOnResourceReady(cb); removeCallback(cb); } decrementPendingCallbacks(); }}}synchronized void callCallbackOnResourceReady(ResourceCallback cb) {
    try {
      // Call back the target data
      // The cb type is singleRequest
      cb.onResourceReady(engineResource, dataSource);
    } catch (Throwable t) {
      throw newCallbackException(t); }}Copy the code

Isn’t it nice to see this (actual scalp tingling)! Here comes the familiar callback, where cb is of type SingleRequest, which we analyzed above. So the onResourceReady method of SingleRequest is called

SingleRequest#onResourceReady

  public synchronized void onResourceReady(Resource
        resource, DataSource dataSource) {...// Focus on
    onResourceReady((Resource<R>) resource, (R) received, dataSource);
  }

  private synchronized void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {
    // First load
    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 the listener is set at use, onResourceReady is called back
      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);

        // Display photos
	    // The target is DrawableImageViewTargettarget.onResourceReady(result, animation); }}finally {
      isCallingCallbacks = false;
    }
    // The notification was successfully loaded
    notifyLoadSuccess();
  }
Copy the code

OnResourceReady (result, animation), target object is DrawableImageViewTarget, So the onResourceReady method of DrawableImageViewTarget is called, but since DrawableImageViewTarget doesn’t have onResourceReady, So it should be in its parent class, ImageViewTarget

ImageViewTarget

  public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
     // Is there any animation effect
    if (transition == null| |! transition.transition(resource,this)) {
	  // Focus on the static graph
      setResourceInternal(resource);
    } else {
      //gif  maybeUpdateAnimatable(resource); }}private void setResourceInternal(@Nullable Z resource) {
    // Call setResource to display the photo
    setResource(resource);
    maybeUpdateAnimatable(resource);
  }

  DrawableImageViewTarget (DrawableImageViewTarget); DrawableImageViewTarget (DrawableImageViewTarget); DrawableImageViewTarget (DrawableImageViewTarget)
  protected abstract void setResource(@Nullable Z resource);
Copy the code

Here we use a normal static image, so we call setResourceInternal(Resource), and then setResource(resource) to show the image, SetResource in ImageViewTarget is an abstract method, so let’s go back to the implementation of subclass DrawableImageViewTarget

DrawableImageViewTarget#setResource

  protected void setResource(@Nullable Drawable resource) {
    // Successfully display the photo
    view.setImageDrawable(resource);
  }
Copy the code

A: wow! The setResource is very simple, which is to display the photo directly!

3. Summary

Into method is the whole Glide picture loading process of the most complex logic of a song, the amount of code, the corresponding workload is also super much, both when the father and mother, both to the network to obtain data, but also to parse and display data. The main work is as follows:

conclusion

It took me a long time to read the Glide3.x version and the source code for Glide3.7, and then read the Glide4.9 article and source code, and finally summarize myself. After reading the source code of the Glide4.9 loading process, I feel that this call is really many, and it is quite time-consuming to retrieve the parameters of the call. But as a whole, there is only one word in my heart, “Glide!” With only one line of code, the actual internal processing logic is how complex and in place, also enough to see how powerful Glide function. But the power of Glide is not only shown in the image loading process, but also in its powerful caching strategy. Let’s continue to appreciate the power of Glide: Glide 4.9 source parse-caching strategy

Reference blog:

  • Glide 4.9 source code analysis (a) – a complete loading process
  • Android source code analysis: this is a detailed picture loading library Glide source code explain walkthroughs
  • Android picture loading framework the most complete analysis (two), from the perspective of the source understanding Glide’s execution process