Analysis of Glide source code process

Posted by Megalink on Tue, 08 Mar 2022 01:37:25 +0100

 

First of all, glide, as a powerful image loading framework, has been officially used by android. Therefore, understanding the loading process and principle of glide is very important to deepen our understanding of glide. Now let's analyze it Glide Execute the process, there is a right in the head first Glide For the understanding of the overall implementation process of the source code, learn from a Glide framework diagram drawn by Gentiana wild dust dream, so that we have a preliminary understanding of the overall framework of Glide

 

First of all, let's start with this sentence and take a look at the initialization process of Glide, that is, Glide With (this) method.

Glide.with(this).load("").into(new ImageView(this));

  with

1 call with

 Glide.with(Activity);

Look at the source function with. There are many overloads.

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

    @NonNull
    public static RequestManager with(@NonNull Activity activity) {
        return getRetriever(activity).get(activity);
    }

    @NonNull
    public static RequestManager with(@NonNull FragmentActivity activity) {
        return getRetriever(activity).get(activity);
    }

    @NonNull
    public static RequestManager with(@NonNull Fragment fragment) {
        return getRetriever(fragment.getActivity()).get(fragment);
    }

    /** @deprecated */
    @Deprecated
    @NonNull
    public static RequestManager with(@NonNull android.app.Fragment fragment) {
        return getRetriever(fragment.getActivity()).get(fragment);
    }

    @NonNull
    public static RequestManager with(@NonNull View view) {
        return getRetriever(view.getContext()).get(view);
    }

In fact, there are three commonly used forms: Activity, fragment and context. Let's focus on Activity.

2 getRetriever(activity)

    @NonNull
    private static RequestManagerRetriever getRetriever(@Nullable Context context) {
        Preconditions.checkNotNull(context, "You cannot start a load on a not yet attached View or a Fragment where getActivity() returns null (which usually occurs when getActivity() is called before the Fragment is attached or after the Fragment is destroyed).");
        return get(context).getRequestManagerRetriever();
    }
    @NonNull
    public static Glide get(@NonNull Context context) {
        if (glide == null) {
            Class var1 = Glide.class;
            synchronized(Glide.class) {
                if (glide == null) {
                    checkAndInitializeGlide(context);
                }
            }
        }

        return glide;
    }
    private static void checkAndInitializeGlide(@NonNull Context context) {
        if (isInitializing) {
            //Initialization exception information will be thrown here
        } else {
          	//Initialize flag
            isInitializing = true;
          	//Start initialization
            initializeGlide(context);
            isInitializing = false;
        }
    }
 private static void initializeGlide(@NonNull Context context) {
      //Instantiate a GlideBuilder for initialization
   		//Some default configuration information of GlideBuilder
   		initializeGlide(context, new GlideBuilder());
    }

Instantiate a GlideBuilder by calling the getRetriever method to initialize the information.

Let's take a look at what initializeGlide(context, new GlideBuilder()) does

 

    private static void initializeGlide(@NonNull Context context, @NonNull GlideBuilder builder) {
        Context applicationContext = context.getApplicationContext();
        //Find the generatedapglidemoduleimpl class through reflection. If it can be found, it means that the GlideModule has been customized
        //Then you need to call applyOptions and registerComponents in the right place to realize the customized functions
        GeneratedAppGlideModule annotationGeneratedModule = getAnnotationGeneratedGlideModules();
        List<com.bumptech.glide.module.GlideModule> manifestModules = Collections.emptyList();
        if (annotationGeneratedModule == null || annotationGeneratedModule.isManifestParsingEnabled()) {
            // From androidmanifest XML to get the customized GlideModule (this is another way to customize the GlideModule)
            manifestModules = new ManifestParser(applicationContext).parse();
        }
		//If the GlideModule to be excluded is specified in the annotation, delete the GlideModule and delete it from androidmanifest XML 
        if (annotationGeneratedModule != null
                && !annotationGeneratedModule.getExcludedModuleClasses().isEmpty()) {
            Set<Class<?>> excludedModuleClasses =
                    annotationGeneratedModule.getExcludedModuleClasses();
            Iterator<com.bumptech.glide.module.GlideModule> iterator = manifestModules.iterator();
            while (iterator.hasNext()) {
                com.bumptech.glide.module.GlideModule current = iterator.next();
                if (!excludedModuleClasses.contains(current.getClass())) {
                    continue;
                }
                if (Log.isLoggable(TAG, Log.DEBUG)) {
                    Log.d(TAG, "AppGlideModule excludes manifest GlideModule: " + current);
                }
                iterator.remove();
            }
        }
		// Get the factory for creating RequestManager (if the with() parameter passes application)
        RequestManagerRetriever.RequestManagerFactory factory =
                annotationGeneratedModule != null
                        ? annotationGeneratedModule.getRequestManagerFactory() : null;
        builder.setRequestManagerFactory(factory);
        //Execute the methods in the GlideModule interface configured by the user in the manifest
        for (com.bumptech.glide.module.GlideModule module : manifestModules) {
            module.applyOptions(applicationContext, builder);
        }
        //Executes methods in the GlideModule interface configured with annotations
        if (annotationGeneratedModule != null) {
            annotationGeneratedModule.applyOptions(applicationContext, builder);
        }
        //1 create Glide and analyze
        Glide glide = builder.build(applicationContext);
        //To execute the registerComponents method below, you need to pass the Registry object, which is assigned and set a series of parameters when creating Glide
        //Execute the registerComponents method in the GlideModule interface configured by the user in the manifest
        for (com.bumptech.glide.module.GlideModule module : manifestModules) {
            module.registerComponents(applicationContext, glide, glide.registry);
        }
        //Execute the registerComponents method in the GlideModule interface configured with annotations
        if (annotationGeneratedModule != null) {
            annotationGeneratedModule.registerComponents(applicationContext, glide, glide.registry);
        }
        applicationContext.registerComponentCallbacks(glide);
        //Assign value to singleton
        Glide.glide = glide;
    }

Through note 1, we can see that a Glide object is created through the builder mode. Now let's see how to implement it internally

Glide build(@NonNull Context context) {
       //Create an actuator to get data from a data source, such as a network request
        if (sourceExecutor == null) {
            sourceExecutor = GlideExecutor.newSourceExecutor();
        }
        //Create an actuator to get data from the local cache
        if (diskCacheExecutor == null) {
            diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();
        }
        if (animationExecutor == null) {
            animationExecutor = GlideExecutor.newAnimationExecutor();
        }
        //Calculate the cache size to be set according to the current machine parameters
        if (memorySizeCalculator == null) {
            memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();
        }

        if (connectivityMonitorFactory == null) {
            connectivityMonitorFactory = new DefaultConnectivityMonitorFactory();
        }
        //Create a Bitmap pool to recycle the pictures cached by LruCache and recycle the pictures into the bitmapPool, so that the memory can be used when creating pictures next time to avoid memory jitter caused by continuous creation and recycling of memory
        if (bitmapPool == null) {
            
            int size = memorySizeCalculator.getBitmapPoolSize();
            if (size > 0) {
                bitmapPool = new LruBitmapPool(size);
            } else {
                bitmapPool = new BitmapPoolAdapter();
            }
        }

        //Create array pool
        if (arrayPool == null) {
            arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes());
        }

        //Create memory cache
        if (memoryCache == null) {
            memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
        }

        //Create disk cache
        if (diskCacheFactory == null) {
            diskCacheFactory = new InternalCacheDiskCacheFactory(context);
        }
        //Create an Engine to really handle the class of request, such as initiating a network request for pictures, reading pictures from disk, etc
        if (engine == null) {
            engine =
                    new Engine(
                            memoryCache,
                            diskCacheFactory,
                            diskCacheExecutor,
                            sourceExecutor,
                            GlideExecutor.newUnlimitedSourceExecutor(),
                            animationExecutor,
                            isActiveResourceRetentionAllowed);
        }

        if (defaultRequestListeners == null) {
            defaultRequestListeners = Collections.emptyList();
        } else {
            defaultRequestListeners = Collections.unmodifiableList(defaultRequestListeners);
        }
        //Code 2 getRetriever() returns this object
        RequestManagerRetriever requestManagerRetriever =
                new RequestManagerRetriever(requestManagerFactory);
		//Create Glide
        return new Glide(
                context,
                engine,
                memoryCache,
                bitmapPool,
                arrayPool,
                requestManagerRetriever,
                connectivityMonitorFactory,
                logLevel,
                defaultRequestOptions.lock(),
                defaultTransitionOptions,
                defaultRequestListeners,
                isLoggingRequestOriginsEnabled);
    }

From the above code and comments, we can see that the builder here mainly constructs thread pool, reuse pool, cache strategy and execution Engine. Now we come to the place where Glide was really created.

Code category 5 Glide.java 
   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,
            @NonNull Map<Class<?>, TransitionOptions<?, ?>> defaultTransitionOptions,
            @NonNull List<RequestListener<Object>> defaultRequestListeners,
            boolean isLoggingRequestOriginsEnabled) {
        this.engine = engine;
        this.bitmapPool = bitmapPool;
        this.arrayPool = arrayPool;
        this.memoryCache = memoryCache;
        this.requestManagerRetriever = requestManagerRetriever;
        this.connectivityMonitorFactory = connectivityMonitorFactory;

        DecodeFormat decodeFormat = defaultRequestOptions.getOptions().get(Downsampler.DECODE_FORMAT);
        bitmapPreFiller = new BitmapPreFiller(memoryCache, bitmapPool, decodeFormat);

        final Resources resources = context.getResources();

        registry = new Registry();
        registry.register(new DefaultImageHeaderParser());

          ...Omit some code and create the code that needs to be added to registry Class of...

        ContentResolver contentResolver = context.getContentResolver();
		//Add various encoders (save data as File), ResourceDecoder (change data from type A to type B)
		//ModelLoaderFactory (used to create ModelLoader, which is used to convert any complex data model into a specific data type of resource data represented by the data fetcher. Used to load resources.)
        registry
                .append(ByteBuffer.class, new ByteBufferEncoder())
                ...Omit several codes...
                .append(Drawable.class, Drawable.class, new UnitDrawableDecoder())
                /* Transcoders */
                .register(
                        Bitmap.class,
                        BitmapDrawable.class,
                        new BitmapDrawableTranscoder(resources))
                .register(Bitmap.class, byte[].class, bitmapBytesTranscoder)
                .register(
                        Drawable.class,
                        byte[].class,
                        new DrawableBytesTranscoder(
                                bitmapPool, bitmapBytesTranscoder, gifDrawableBytesTranscoder))
                .register(GifDrawable.class, byte[].class, gifDrawableBytesTranscoder);
        //The factory is used to produce ImageViewTarget, and finally add the picture to the interface through the ImageViewTarget object 
        ImageViewTargetFactory imageViewTargetFactory = new ImageViewTargetFactory();
        glideContext =
                new GlideContext(
                        context,
                        arrayPool,
                        registry,
                        imageViewTargetFactory,
                        defaultRequestOptions,
                        defaultTransitionOptions,
                        defaultRequestListeners,
                        engine,
                        isLoggingRequestOriginsEnabled,
                        logLevel);
    }

As you can see, the code is mainly a Registry object. Call append and register function to set some parameters. These parameters are mainly used to analyze and load the following pictures

 

Analyze append method:

  public <Model, Data> Registry append(
      @NonNull Class<Model> modelClass,
      @NonNull Class<Data> dataClass,
      @NonNull ModelLoaderFactory<Model, Data> factory) {
    modelLoaderRegistry.append(modelClass, dataClass, factory);
    return this;
  }

The append method of ModeLoaderRegistry is called directly:

  public synchronized <Model, Data> void append(
      @NonNull Class<Model> modelClass,
      @NonNull Class<Data> dataClass,
      @NonNull ModelLoaderFactory<? extends Model, ? extends Data> factory) {
    multiModelLoaderFactory.append(modelClass, dataClass, factory);
    cache.clear();
  }

A MultiModelLoaderFactory is encapsulated in the ModelLoaderRegistry. There is a list in the MultiModelLoaderFactory. Everything that comes in the append is finally put into the list. The cache is just to improve the cache efficiency. Specifically, see the source code of ModelLoaderRegistry and MultiModelLoaderFactory, and the key methods are annotated.

There is no difficulty in the append method of Registry. This is put in, so how do we get it out?

This is the getModelLoaders method of Registry, which obtains a List of modelloaders according to the Model type. You can see the source code of ModelLoader Registry

package com.bumptech.glide.load.model;
 
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.Pools.Pool;
import com.bumptech.glide.Registry.NoModelLoaderAvailableException;
import com.bumptech.glide.util.Synthetic;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
/**
 * Maintains an ordered put of {@link ModelLoader}s and the model and data types they handle in
 * order from highest priority to lowest.
 */
// Hides Model throughout.
@SuppressWarnings("TypeParameterHidesVisibleType")
public class ModelLoaderRegistry {
 
  private final MultiModelLoaderFactory multiModelLoaderFactory;
  private final ModelLoaderCache cache = new ModelLoaderCache();
 
  public ModelLoaderRegistry(@NonNull Pool<List<Throwable>> throwableListPool) {
    this(new MultiModelLoaderFactory(throwableListPool));
  }
 
  private ModelLoaderRegistry(@NonNull MultiModelLoaderFactory multiModelLoaderFactory) {
    this.multiModelLoaderFactory = multiModelLoaderFactory;
  }
 
  public synchronized <Model, Data> void append(
      @NonNull Class<Model> modelClass,
      @NonNull Class<Data> dataClass,
      @NonNull ModelLoaderFactory<? extends Model, ? extends Data> factory) {
    multiModelLoaderFactory.append(modelClass, dataClass, factory);
    cache.clear();
  }
 
  public synchronized <Model, Data> void prepend(
      @NonNull Class<Model> modelClass,
      @NonNull Class<Data> dataClass,
      @NonNull ModelLoaderFactory<? extends Model, ? extends Data> factory) {
    multiModelLoaderFactory.prepend(modelClass, dataClass, factory);
    cache.clear();
  }
 
  public synchronized <Model, Data> void remove(
      @NonNull Class<Model> modelClass, @NonNull Class<Data> dataClass) {
    tearDown(multiModelLoaderFactory.remove(modelClass, dataClass));
    cache.clear();
  }
 
  public synchronized <Model, Data> void replace(
      @NonNull Class<Model> modelClass,
      @NonNull Class<Data> dataClass,
      @NonNull ModelLoaderFactory<? extends Model, ? extends Data> factory) {
    tearDown(multiModelLoaderFactory.replace(modelClass, dataClass, factory));
    cache.clear();
  }
 
  private <Model, Data> void tearDown(
      @NonNull List<ModelLoaderFactory<? extends Model, ? extends Data>> factories) {
    for (ModelLoaderFactory<? extends Model, ? extends Data> factory : factories) {
      factory.teardown();
    }
  }
 
  // We're allocating in a loop to avoid allocating empty lists that will never have anything added
  // to them.
  @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
  @NonNull
  public <A> List<ModelLoader<A, ?>> getModelLoaders(@NonNull A model) {
    List<ModelLoader<A, ?>> modelLoaders = getModelLoadersForClass(getClass(model));
    if (modelLoaders.isEmpty()) {
      throw new NoModelLoaderAvailableException(model);
    }
    int size = modelLoaders.size();
    boolean isEmpty = true;
    List<ModelLoader<A, ?>> filteredLoaders = Collections.emptyList();
    /*
     *What does this note mean
     * https://stackoverflow.com/questions/17836373/what-does-noinspection-forloopreplaceablebyforeach-mean
     */
    //noinspection ForLoopReplaceableByForEach to improve perf
    for (int i = 0; i < size; i++) {
      ModelLoader<A, ?> loader = modelLoaders.get(i);
      if (loader.handles(model)) {
        if (isEmpty) {
          //A small optimization has been made in this area. The size is specified during initialization to avoid copying during later expansion
          filteredLoaders = new ArrayList<>(size - i);
          isEmpty = false;
        }
        filteredLoaders.add(loader);
      }
    }
    if (filteredLoaders.isEmpty()) {
      throw new NoModelLoaderAvailableException(model, modelLoaders);
    }
    return filteredLoaders;
  }
 
  public synchronized <Model, Data> ModelLoader<Model, Data> build(
      @NonNull Class<Model> modelClass, @NonNull Class<Data> dataClass) {
    return multiModelLoaderFactory.build(modelClass, dataClass);
  }
 
  @NonNull
  public synchronized List<Class<?>> getDataClasses(@NonNull Class<?> modelClass) {
    return multiModelLoaderFactory.getDataClasses(modelClass);
  }
 
  @NonNull
  private synchronized <A> List<ModelLoader<A, ?>> getModelLoadersForClass(
      @NonNull Class<A> modelClass) {
    //The idea of caching is used again. If there is no new modelClass added, it is needed in the cache
    //If you don't get from the cache, you need to call the build of multiModelLoaderFactory. In this way, you may instantiate multiple modelloaders, which will be very expensive
    List<ModelLoader<A, ?>> loaders = cache.get(modelClass);
    if (loaders == null) {
      loaders = Collections.unmodifiableList(multiModelLoaderFactory.build(modelClass));
      cache.put(modelClass, loaders);
    }
    return loaders;
  }
 
  @NonNull
  @SuppressWarnings("unchecked")
  private static <A> Class<A> getClass(@NonNull A model) {
    return (Class<A>) model.getClass();
  }
 
  private static class ModelLoaderCache {
    private final Map<Class<?>, Entry<?>> cachedModelLoaders = new HashMap<>();
 
    @Synthetic
    ModelLoaderCache() {}
 
    public void clear() {
      cachedModelLoaders.clear();
    }
 
    public <Model> void put(Class<Model> modelClass, List<ModelLoader<Model, ?>> loaders) {
      Entry<?> previous = cachedModelLoaders.put(modelClass, new Entry<>(loaders));
      if (previous != null) {
        throw new IllegalStateException("Already cached loaders for model: " + modelClass);
      }
    }
 
    @Nullable
    @SuppressWarnings("unchecked")
    public <Model> List<ModelLoader<Model, ?>> get(Class<Model> modelClass) {
      Entry<Model> entry = (Entry<Model>) cachedModelLoaders.get(modelClass);
      return entry == null ? null : entry.loaders;
    }
 
    private static class Entry<Model> {
      @Synthetic final List<ModelLoader<Model, ?>> loaders;
 
      public Entry(List<ModelLoader<Model, ?>> loaders) {
        this.loaders = loaders;
      }
    }
  }
}

MultiModelLoaderFactory is a very important class. Firstly, it stores all the information of modelClass, dataClass and ModelLoaderFactory in the entries List. There are also two important overloaded methods, load.

synchronized <Model> List<ModelLoader<Model, ?>> build(@NonNull Class<Model> modelClass)

According to the modelClass type, all modelloaders of modelClass type in all entries List are built through the factory class ModelLoaderFactory method, and returned in the loaders List.

It should be noted that the build implementation of different modelloaderfactories is different, and the MultiModelLoaderFactory object passed in the build method.

3 RequestManager getRetriever(activity).get(activity);

There are also many overloaded functions in get here. Let's look at the overload of Activity parameters.

public class RequestManagerRetriever implements Handler.Callback {
  


  @NonNull
  public RequestManager get(@NonNull Context context) {
   if (context == null) {
      throw new IllegalArgumentException("You cannot start a load on a null Context");
     //If it is in the main thread and is not executed for Application level Context
    } else if (Util.isOnMainThread() && !(context instanceof Application)) {
      if (context instanceof FragmentActivity) {
        return get((FragmentActivity) context);
      } else if (context instanceof Activity) {
        return get((Activity) context);
      } else if (context instanceof ContextWrapper) {
        //Until you find BaseContext
        return get(((ContextWrapper) context).getBaseContext());
      }
    }
		//If it is not in the main thread or for Application, it will be executed directly
    return getApplicationManager(context);
  }

  @NonNull
  public RequestManager get(@NonNull FragmentActivity activity) {
		....
  }

  @NonNull
  public RequestManager get(@NonNull Fragment fragment) {
	....
  }

  //Get RequestManager through Activity
  @SuppressWarnings("deprecation")
  @NonNull
  public RequestManager get(@NonNull Activity activity) {
    //Determines whether a task is currently requested in a child thread
    if (Util.isOnBackgroundThread()) {
      //Load through Application level Context
      return get(activity.getApplicationContext());
    } else {
      //Check whether the Activity has been destroyed
      assertNotDestroyed(activity);
      //Get the FragmentManager of the current Activity 
      android.app.FragmentManager fm = activity.getFragmentManager();
      //It is mainly to generate a Fragment and then bind a request management RequestManager
      return fragmentGet(
          activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
    }
  }
}

Let's take a look at the implementation of the fragmentGet function

  private RequestManager fragmentGet(@NonNull Context context,
      @NonNull android.app.FragmentManager fm,
      @Nullable android.app.Fragment parentHint,
      boolean isParentVisible) {
    //1. Add a Fragment to the current Acitivty to manage the life cycle of the request
    RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
    //Get the management class of the current request
    RequestManager requestManager = current.getRequestManager();
    //If it does not exist, a request is created. The manager is kept in the Fragment of the current management life cycle, which is equivalent to binding the two to avoid memory leakage.
    if (requestManager == null) {
      Glide glide = Glide.get(context);
      requestManager =
          factory.build(
              glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
      current.setRequestManager(requestManager);
    }
    //Returns the manager of the current request
    return requestManager;
  }

From the above code, we can see that it is used to manage the life cycle of the request. Let's see how to add the Fragment to the Activity.

  private RequestManagerFragment getRequestManagerFragment(
      @NonNull final android.app.FragmentManager fm,
      @Nullable android.app.Fragment parentHint,
      boolean isParentVisible) {
    //Get the instantiated Fragment through TAG, which is equivalent to if the same activity glide with.. Multiple times, then there is no need to create multiple.
    RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
    //If you don't get the Fragment of the management request life cycle in the current Activity, you can see whether there is one from the cache
    if (current == null) {
      current = pendingRequestManagerFragments.get(fm);
      //If the cache is not available, you can instantiate a Fragment directly
      if (current == null) {
        current = new RequestManagerFragment();
        current.setParentFragmentHint(parentHint);
        //Start if there are already requests to execute
        if (isParentVisible) {
          current.getGlideLifecycle().onStart();
        }
        //Add to Map cache
        pendingRequestManagerFragments.put(fm, current);
        //Start submitting and adding a Fragment container through the Fragment manager of the current Activity
        fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
        //Successfully added to FragmentManager, send to clean cache.
        handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
      }
    }
    return current;
  }

Finally, it's time to bind the request management class to the Fragment.

  private RequestManager fragmentGet(@NonNull Context context,
      @NonNull android.app.FragmentManager fm,
      @Nullable android.app.Fragment parentHint,
      boolean isParentVisible) {
    ...
    //If it does not exist, a request is created. The manager is kept in the Fragment of the current management life cycle, which is equivalent to binding the two to avoid memory leakage.
    if (requestManager == null) {
      //Get a single case, Glide
      Glide glide = Glide.get(context);
      //Build request management, current Getglidelifecycle() is the ActivityFragmentLifecycle class, which we will talk about later
      requestManager =
          factory.build(
              glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
      //Bind the built request management in the Fragment.
      current.setRequestManager(requestManager);
    }
    //Returns the manager of the current request
    return requestManager;
  }

Let's see how to build a request management class

  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) {
      //instantiation 
      return new RequestManager(glide, lifecycle, requestManagerTreeNode, context);
    }
  };
public RequestManager(
      @NonNull Glide glide, @NonNull Lifecycle lifecycle,
      @NonNull RequestManagerTreeNode treeNode, @NonNull Context context) {
    this(
        glide,
        lifecycle,
        treeNode,
        new RequestTracker(),
        glide.getConnectivityMonitorFactory(),
        context);
  }

  // Our usage is safe here.
  @SuppressWarnings("PMD.ConstructorCallsOverridableMethod")
  RequestManager(
      Glide glide,
      Lifecycle lifecycle,
      RequestManagerTreeNode treeNode,
      RequestTracker requestTracker,
      ConnectivityMonitorFactory factory,
      Context context) {
    this.glide = glide;
    this.lifecycle = lifecycle;
    this.treeNode = treeNode;
    this.requestTracker = requestTracker;
    this.context = context;

    connectivityMonitor =
        factory.build(
            context.getApplicationContext(),
            new RequestManagerConnectivityListener(requestTracker));

    //Here, as long as the life cycle monitoring is added, the Fragment is passed
    if (Util.isOnBackgroundThread()) {
      mainHandler.post(addSelfToLifecycle);
    } else {
      lifecycle.addListener(this);
    }
    //Add monitoring for network changes
    lifecycle.addListener(connectivityMonitor);

    defaultRequestListeners =
        new CopyOnWriteArrayList<>(glide.getGlideContext().getDefaultRequestListeners());
    setRequestOptions(glide.getGlideContext().getDefaultRequestOptions());
		
    glide.registerRequestManager(this);
  }

Here, the request management class RequestManager + Fragment has been bound successfully, and the declaration cycle listening has also been set.

How do they ensure the delivery of the life cycle? We mainly look at the Fragment life cycle method

//Why monitor the life cycle of Fragment? In fact, we should also know that Fragment is attached to Activity, and the life cycle of Activity is in Fragment, so it's OK to monitor Fragment.
public class RequestManagerFragment extends Fragment {
  
  //Equivalent to lifecycle callback
	private final ActivityFragmentLifecycle lifecycle;
  
  ....


  @Override
  public void onStart() {
    super.onStart();
    lifecycle.onStart();
  }

  @Override
  public void onStop() {
    super.onStop();
    lifecycle.onStop();
  }

  @Override
  public void onDestroy() {
    super.onDestroy();
    lifecycle.onDestroy();
  }
  
  ...
}

The lifecycle here is responsible for the processing of the life cycle. Let's take a closer look

class ActivityFragmentLifecycle implements Lifecycle {
  private final Set<LifecycleListener> lifecycleListeners =
      Collections.newSetFromMap(new WeakHashMap<LifecycleListener, Boolean>());
  private boolean isStarted;
  private boolean isDestroyed;


  @Override
  public void addListener(@NonNull LifecycleListener listener) {
    lifecycleListeners.add(listener);

    if (isDestroyed) {
      listener.onDestroy();
    } else if (isStarted) {
      listener.onStart();
    } else {
      listener.onStop();
    }
  }

  @Override
  public void removeListener(@NonNull LifecycleListener listener) {
    lifecycleListeners.remove(listener);
  }

  void onStart() {
    isStarted = true;
    for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
      lifecycleListener.onStart();
    }
  }

  void onStop() {
    isStarted = false;
    for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
      lifecycleListener.onStop();
    }
  }

  void onDestroy() {
    isDestroyed = true;
    for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
      lifecycleListener.onDestroy();
    }
  }
}

You know, it implements the Lifecycle life cycle interface in Glide. The registration is the RequestManager instantiated in the RequestManagerFactory factory, and then adds the Lifecycle callback listening in the constructor. See the following for details.

public class RequestManager implements LifecycleListener,
    ModelTypes<RequestBuilder<Drawable>> {
      
      ...
         @Override
  public synchronized void onStart() {
    resumeRequests();
    targetTracker.onStart();
  }


  @Override
  public synchronized void onStop() {
    pauseRequests();
    targetTracker.onStop();
  }


  @Override
  public synchronized void onDestroy() {
    targetTracker.onDestroy();
    for (Target<?> target : targetTracker.getAll()) {
      clear(target);
    }
    targetTracker.clear();
    requestTracker.clearRequests();
    lifecycle.removeListener(this);
    lifecycle.removeListener(connectivityMonitor);
    mainHandler.removeCallbacks(addSelfToLifecycle);
    glide.unregisterRequestManager(this);
  }
        
     	...
    }

II. load

The load function is relatively simple to load. Let's look at the specific code implementation

public class RequestManager implements LifecycleListener,
    ModelTypes<RequestBuilder<Drawable>> {
      
  .....
    
  public RequestBuilder<Drawable> load(@Nullable String string) {
    //Here, the Drawable picture loading requester is called to load it
    return asDrawable().load(string);
  }
  
  public RequestBuilder<Drawable> asDrawable() {
    return as(Drawable.class);
  }

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


  @NonNull
  @CheckResult
  @Override
  public RequestBuilder<Drawable> load(@Nullable File file) {
    return asDrawable().load(file);
  }
    
  public <ResourceType> RequestBuilder<ResourceType> as(
      @NonNull Class<ResourceType> resourceClass) {
    return new RequestBuilder<>(glide, this, resourceClass, context);
  }
          
}

You can see that load also has many overloaded functions. It has to be said that it is really powerful. As long as you give a picture resource, it can be loaded for you.

public class RequestBuilder<TranscodeType> extends BaseRequestOptions<RequestBuilder<TranscodeType>>
    implements Cloneable, ModelTypes<RequestBuilder<TranscodeType>> {
        
  public RequestBuilder<TranscodeType> load(@Nullable String string) {
    return loadGeneric(string);
  }
  
  // Describe the loaded data source - it can be regarded as the data source we just passed in http://xxxx.png
  @Nullable private Object model;
  // Describes whether a loaded data source has been added to this request
  private boolean isModelSet;
  
  private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
    this.model = model;
    isModelSet = true;
    return this;
  }
}

OK, here, the RequestBuilder is built. Next, we'll wait for the request to be executed. Let's take a look at its RequestBuilder's into method.

 

Three into

public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
    Util.assertMainThread();
    Preconditions.checkNotNull(view);

    BaseRequestOptions<?> requestOptions = this;
    if (!requestOptions.isTransformationSet()
        && requestOptions.isTransformationAllowed()
        && view.getScaleType() != null) {
   //Set the parameters according to the ScaleType type of ImageView. Note that here is a new object of clone, which operates on the new object
     
      switch (view.getScaleType()) {
        case CENTER_CROP:
          requestOptions = requestOptions.clone().optionalCenterCrop();
          break;
        case CENTER_INSIDE:
          requestOptions = requestOptions.clone().optionalCenterInside();
          break;
        case FIT_CENTER:
        case FIT_START:
        case FIT_END:
          requestOptions = requestOptions.clone().optionalFitCenter();
          break;
        case FIT_XY:
          requestOptions = requestOptions.clone().optionalCenterInside();
          break;
        case CENTER:
        case MATRIX:
        default:
          // Do nothing.
      }
    }
           1
    return into(
        
        glideContext.buildImageViewTarget(view, transcodeClass),
        /*targetListener=*/ null,
        requestOptions,
        Executors.mainThreadExecutor());
  }
 public <X> ViewTarget<ImageView, X> buildImageViewTarget(
      @NonNull ImageView imageView, @NonNull Class<X> transcodeClass) {
    return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
  }
  public <Z> ViewTarget<ImageView, Z> buildTarget(@NonNull ImageView view,
      @NonNull Class<Z> clazz) {
    if (Bitmap.class.equals(clazz)) {
      return (ViewTarget<ImageView, Z>) new BitmapImageViewTarget(view);
    } else if (Drawable.class.isAssignableFrom(clazz)) {
      //Because asDrawable was called earlier, this ViewTarget will be created
      return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);
    } else {
      throw new IllegalArgumentException(
          "Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)");
    }
  }

In the above steps, create BitmapImageViewTarget and DrawableImageViewTarget through ImageViewTargetFactory.

First get the property of the current ImageView getScaleType type, and then re clone it for configuration; Call into overload to continue building

Back to 1

    private <Y extends Target<TranscodeType>> Y into(
            @NonNull Y target,
            @Nullable RequestListener<TranscodeType> targetListener,
            BaseRequestOptions<?> options,
            Executor callbackExecutor) {
        Preconditions.checkNotNull(target);
        if (!isModelSet) {
            //The load function has been called, and this variable is true
            throw new IllegalArgumentException("You must call #load() before calling #into()");
        }
		//Create a request to display a picture. Pictures may be from the cache or from the network
        Request request = buildRequest(target, targetListener, options, callbackExecutor);
        
		//Obtain the request corresponding to the target and compare it with the current request
        Request previous = target.getRequest();
        if (request.isEquivalentTo(previous)
                && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
            request.recycle();
            // If the request is completed, beginning again will ensure the result is re-delivered,
            // triggering RequestListeners and Targets. If the request is failed, beginning again will
            // restart the request, giving it another chance to complete. If the request is already
            // running, we can let it continue running without interruption.
            if (!Preconditions.checkNotNull(previous).isRunning()) {
                // Use the previous request rather than the new one to allow for optimizations like skipping
                // setting placeholders, tracking and un-tracking Targets, and obtaining View dimensions
                // that are done in the individual Request.
                previous.begin();
            }
            return target;
        }

        requestManager.clear(target);
        //Set the current request to target (ViewTarget)
        target.setRequest(request);
        //2. Perform image request operation 
        requestManager.track(target, request);

        return target;
    }
  1. Build a Glide request for target buildRequest;
  2. Submit the constructed Request to the RequestManager for execution

Now look at the operation created by request

private Request buildRequest(
      Target<TranscodeType> target,
      @Nullable RequestListener<TranscodeType> targetListener,
      BaseRequestOptions<?> requestOptions,
      Executor callbackExecutor) {
    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,
      int overrideHeight,
      BaseRequestOptions<?> requestOptions,
      Executor callbackExecutor) {

    // Build the ErrorRequestCoordinator first if necessary so we can update parentCoordinator.
    ErrorRequestCoordinator errorRequestCoordinator = null;
    if (errorBuilder != null) {
      errorRequestCoordinator = new ErrorRequestCoordinator(parentCoordinator);
      parentCoordinator = errorRequestCoordinator;
    }

    Request mainRequest =
        buildThumbnailRequestRecursive(
            target,
            targetListener,
            parentCoordinator,
            transitionOptions,
            priority,
            overrideWidth,
            overrideHeight,
            requestOptions,
            callbackExecutor);

    if (errorRequestCoordinator == null) {
      return mainRequest;
    }

    int errorOverrideWidth = errorBuilder.getOverrideWidth();
    int errorOverrideHeight = errorBuilder.getOverrideHeight();
    if (Util.isValidDimensions(overrideWidth, overrideHeight)
        && !errorBuilder.isValidOverride()) {
      errorOverrideWidth = requestOptions.getOverrideWidth();
      errorOverrideHeight = requestOptions.getOverrideHeight();
    }

    Request errorRequest =
        errorBuilder.buildRequestRecursive(
            target,
            targetListener,
            errorRequestCoordinator,
            errorBuilder.transitionOptions,
            errorBuilder.getPriority(),
            errorOverrideWidth,
            errorOverrideHeight,
            errorBuilder,
            callbackExecutor);
    errorRequestCoordinator.setRequests(mainRequest, errorRequest);
    return errorRequestCoordinator;
  }

 

    private Request buildThumbnailRequestRecursive(
            Target<TranscodeType> target,
            RequestListener<TranscodeType> targetListener,
            @Nullable RequestCoordinator parentCoordinator,
            TransitionOptions<?, ? super TranscodeType> transitionOptions,
            Priority priority,
            int overrideWidth,
            int overrideHeight,
            BaseRequestOptions<?> requestOptions,
            Executor callbackExecutor) {
        if (thumbnailBuilder != null) {
            // Recursive case: contains a potentially recursive thumbnail request builder.
            TransitionOptions<?, ? super TranscodeType> thumbTransitionOptions =
                    thumbnailBuilder.transitionOptions;

            // Apply our transition by default to thumbnail requests but avoid overriding custom options
            // that may have been applied on the thumbnail request explicitly.
            if (thumbnailBuilder.isDefaultTransitionOptionsSet) {
                thumbTransitionOptions = transitionOptions;
            }
			//Take out the configuration, priority, width and height of thumbnails
            Priority thumbPriority = thumbnailBuilder.isPrioritySet()
                    ? thumbnailBuilder.getPriority() : getThumbnailPriority(priority);

            int thumbOverrideWidth = thumbnailBuilder.getOverrideWidth();
            int thumbOverrideHeight = thumbnailBuilder.getOverrideHeight();
            if (Util.isValidDimensions(overrideWidth, overrideHeight)
                    && !thumbnailBuilder.isValidOverride()) {
                thumbOverrideWidth = requestOptions.getOverrideWidth();
                thumbOverrideHeight = requestOptions.getOverrideHeight();
            }

            ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);
            //Create a Request using the parameters of the thumbnail
            Request fullRequest =
                    obtainRequest(
                            target,
                            targetListener,
                            requestOptions,
                            coordinator,
                            transitionOptions,
                            priority,
                            overrideWidth,
                            overrideHeight,
                            callbackExecutor);
            isThumbnailBuilt = true;
            // Recursively generate thumbnail requests.
            //Because of the call When thumbnail passes the GlideRequest parameter, the Request may also set thumbnails and error diagrams (that is, thumbnails of thumbnails), so the Request is created recursively here
            Request thumbRequest =
                    thumbnailBuilder.buildRequestRecursive(
                            target,
                            targetListener,
                            coordinator,
                            thumbTransitionOptions,
                            thumbPriority,
                            thumbOverrideWidth,
                            thumbOverrideHeight,
                            thumbnailBuilder,
                            callbackExecutor);
            isThumbnailBuilt = false;
            coordinator.setRequests(fullRequest, thumbRequest);
            return coordinator;
        } 

         ... Some codes are omitted below, which are created according to other situations Request ...
    }

Continue tracking

  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) {
    return SingleRequest.obtain(
        context,
        glideContext,
        model,
        transcodeClass,
        requestOptions,
        overrideWidth,
        overrideHeight,
        priority,
        target,
        targetListener,
        requestListeners,
        requestCoordinator,
        glideContext.getEngine(),
        transitionOptions.getTransitionFactory(),
        callbackExecutor);
  }

Finally, we found that it was singlerequest For the Request object built for us, we just initialize some configuration properties. Let's find the place marked 2 at the beginning and look at the execution of the # track # function first.

//A synchronization lock is added to the current class to avoid security caused by threads  
synchronized void track(@NonNull Target<?> target, @NonNull Request request) {
  	//Add a target task  
  	targetTracker.track(target);
  	//Execute Glide request
    requestTracker.runRequest(request);
  }
  public void runRequest(@NonNull Request request) {
    //Add a request
    requests.add(request);
    //Pause
    if (!isPaused) {
      //Without pause, start calling Request begin for execution
      request.begin();
    } else {
      //If pause is invoked, clean up the request
      request.clear();
      pendingRequests.add(request);
    }
  }

The above logic is to add a request for # requests # to see if it is in the stop state. If not, call # request begin(); Execution.

Request here is an interface. We can know that the implementation class of request is SingleRequest by talking about the buildRequest function. Let's directly look at its begin function

    public synchronized void begin() {
        ...
        if (model == null) {

            ...
            // The model (url) is empty, and the callback failed to load
           onLoadFailed(new GlideException("Received null   model"), logLevel);
           return;
        }
        if (status == Status.RUNNING) {
            throw new IllegalArgumentException("Cannot restart a running request");
        }

        // If we're restarted after we're complete (usually via something like a notifyDataSetChanged
        // that starts an identical request into the same Target or View), we can simply use the
        // resource and size we retrieved the last time around and skip obtaining a new size, starting a
        // new load etc. This does mean that users who want to restart a load because they expect that
        // the view size has changed will need to explicitly clear the View or Target before starting
        // the new load.
        if (status == Status.COMPLETE) {
            
            onResourceReady(resource, DataSource.MEMORY_CACHE);
            return;
        }

        // Restarts for requests that are neither complete nor running can be treated as new requests
        // and can run again from the beginning.

        status = Status.WAITING_FOR_SIZE;
        //The glide will be cached according to the width and height of the displayed image, so here you need to obtain the width and height of the View. overrideWidth and overrideHeight are - 1 by default
        //So for the first time, it will go to the else branch to obtain the width and height of the View
        if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
            // When the override() API is used to specify a fixed width and height for the picture, onSizeReady is executed directly,
            // The final core processing is located in onSizeReady
            onSizeReady(overrideWidth, overrideHeight);
        } else {
            // Calculate the width and height of the image according to the width and height of imageView, and finally go to onSizeReady
            target.getSize(this);
        }

        if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
                && canNotifyStatusChanged()) {
             // Preload set thumbnails
            target.onLoadStarted(getPlaceholderDrawable());
        }
    }

Let's move on to onSizeReady

    public synchronized void onSizeReady(int width, int height) {

        if (status != Status.WAITING_FOR_SIZE) {
            return;
        }
        //Set status to requesting
        status = Status.RUNNING;
		//Width setting
        float sizeMultiplier = requestOptions.getSizeMultiplier();
        this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
        this.height = maybeApplySizeMultiplier(height, sizeMultiplier);
		
		//The engine here is created by build() when creating Glide. The engine encapsulates various executors, memory cache, etc
        loadStatus =
                engine.load(
                        glideContext,
                        model,
                        requestOptions.getSignature(),
                        this.width,
                        this.height,
                        requestOptions.getResourceClass(),
                        transcodeClass,
                        priority,
                        requestOptions.getDiskCacheStrategy(),
                        requestOptions.getTransformations(),
                        requestOptions.isTransformationRequired(),
                        requestOptions.isScaleOnlyOrNoTransform(),
                        requestOptions.getOptions(),
                        requestOptions.isMemoryCacheable(),
                        requestOptions.getUseUnlimitedSourceGeneratorsPool(),
                        requestOptions.getUseAnimationPool(),
                        requestOptions.getOnlyRetrieveFromCache(),
                        this,
                        callbackExecutor);

        // This is a hack that's only useful for testing right now where loads complete synchronously
        // even though under any executor running on any thread but the main thread, the load would
        // have completed asynchronously.
        if (status != Status.RUNNING) {
            loadStatus = null;
        }
    }

Then look at load

  public synchronized <R> LoadStatus load(
      GlideContext glideContext,
      Object model,
      Key signature,
      int width,
      int height,
      Class<?> resourceClass,
      Class<R> transcodeClass,
      Priority priority,
      DiskCacheStrategy diskCacheStrategy,
      Map<Class<?>, Transformation<?>> transformations,
      boolean isTransformationRequired,
      boolean isScaleOnlyOrNoTransform,
      Options options,
      boolean isMemoryCacheable,
      boolean useUnlimitedSourceExecutorPool,
      boolean useAnimationPool,
      boolean onlyRetrieveFromCache,
      ResourceCallback cb,
      Executor callbackExecutor) {

    //Get the cache or requested key
    EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);
		//Get the resources in the active cache according to the key
    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    //If there is in the ActiveResources active cache, it will be called back
    if (active != null) {
      cb.onResourceReady(active, DataSource.MEMORY_CACHE);
      return null;
    }

    //Try to find this resource from LruResourceCache 
    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
      //If there are resources in the memory cache Lru, call back
      cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
      return null;
    }

    //-------------This shows that neither the active cache nor the memory cache can be found-----------
    
    //Check whether the execution is in progress in the cache according to the Key
    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
    if (current != null) {
      //If the callback is executing, send out the data
      current.addCallback(cb, callbackExecutor);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Added to existing load", startTime, key);
      }
      return new LoadStatus(cb, current);
    }

    // --------------Coming here shows that it is a new task---------------
    // --------------Build a new request task---------------
    EngineJob<R> engineJob =
        engineJobFactory.build(
            key,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache);

    DecodeJob<R> decodeJob =
        decodeJobFactory.build(
            glideContext,
            model,
            key,
            signature,
            width,
            height,
            resourceClass,
            transcodeClass,
            priority,
            diskCacheStrategy,
            transformations,
            isTransformationRequired,
            isScaleOnlyOrNoTransform,
            onlyRetrieveFromCache,
            options,
            engineJob);
		//Add the key to be executed into the cache
    jobs.put(key, engineJob);
		//Callback to execute task
    engineJob.addCallback(cb, callbackExecutor);
    //Start execution.
    engineJob.start(decodeJob);

    return new LoadStatus(cb, engineJob);
  }

Through engine The logic in this function can be summarized as follows:

  1. First build the request or cache the KEY;
  2. According to the KEY, find the corresponding resource data from the memory cache (ActiveResources (active cache, internal is a Map held by weak reference), LruResourceCache). If there is any, call back the onResourceReady of the corresponding listening, indicating that the data is ready.
  3. Find the task corresponding to the key from the execution cache
    1. If it is found, it indicates that it is already being implemented and does not need to be repeated.
    2. Not found, through enginejob Start starts a new request task execution.

Then look down

engine .start 

  public synchronized void start(DecodeJob<R> decodeJob) {
        this.decodeJob = decodeJob;
        //If you can get data from disk cache, use diskCacheExecutor
        //Otherwise, judge which Executor to use according to other conditions
        GlideExecutor executor = decodeJob.willDecodeFromCache()
                ? diskCacheExecutor
                : getActiveSourceExecutor();
        executor.execute(decodeJob);
    }

   boolean willDecodeFromCache() {
        //First look at the following getNextStage analysis
        Stage firstStage = getNextStage(Stage.INITIALIZE);
        //It's clear here. If it can be read from the disk cache, it returns true
        return firstStage == Stage.RESOURCE_CACHE || firstStage == Stage.DATA_CACHE;
    }

Next, go to get NextState to get the next stage. Where should I get the data

    private Stage getNextStage(Stage current) {
        switch (current) {
            case INITIALIZE:
            //At the initial stage, it depends on the disk cache strategy and whether the resource cache can be obtained in the disk (that is, the parsed cache)
                return diskCacheStrategy.decodeCachedResource()
                        ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
            case RESOURCE_CACHE:
            //At present, it is a resource cache. Let's see if we can get the source data cache from the disk cache in the next step
                return diskCacheStrategy.decodeCachedData()
                        ? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
            case DATA_CACHE:
                // Skip loading from source if the user opted to only retrieve the resource from cache.
                //At present, it is data cache. Can I get data from the source data in the next step, such as from the server
                return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
            case SOURCE:
            case FINISHED:
                return Stage.FINISHED;
            default:
                throw new IllegalArgumentException("Unrecognized stage: " + current);
        }
    }

Let's take a look at # enginejob Start specific execution logic:

  public synchronized void start(DecodeJob<R> decodeJob) {
    this.decodeJob = decodeJob;
    //Get the thread pool executed by Glide
    GlideExecutor executor = decodeJob.willDecodeFromCache()
        ? diskCacheExecutor
        : getActiveSourceExecutor();
    //Start execution
    executor.execute(decodeJob);
  }

Here we see the method to implement run in the GlideExecutor thread pool

class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback,
    Runnable,
    Comparable<DecodeJob<?>>,
    Poolable {
      
  //Thread execution calls run
@Override
  public void run() {

    GlideTrace.beginSectionFormat("DecodeJob#run(model=%s)", model);
   
    DataFetcher<?> localFetcher = currentFetcher;
    try {
      //Cancel the current request
      if (isCancelled) {
        notifyFailed();
        return;
      }
      //implement
      runWrapped();
    } catch (CallbackException e) {
      
      
     .....//Some error callbacks
  }
    }

Then look at runWrapped

    /**
     * runReason is INITIALIZE for the first time after initialization
     */
    private void runWrapped() {
        runWrappedCount++;
        switch (runReason) {
            case INITIALIZE:
                //Get the status of the next phase
                stage = getNextStage(Stage.INITIALIZE);
                //According to the status of the next stage, determine which class is executed
                currentGenerator = getNextGenerator();
                runGenerators();
                break;
            case SWITCH_TO_SOURCE_SERVICE:
                runGenerators();
                break;
            case DECODE_DATA:
                decodeFromRetrievedData();
                break;
            default:
                throw new IllegalStateException("Unrecognized run reason: " + runReason);
        }
    }

    private DataFetcherGenerator getNextGenerator() {
        switch (stage) {
            //Get resource data from disk cache
            case RESOURCE_CACHE:
                return new ResourceCacheGenerator(decodeHelper, this);
            //Get source data from disk cache
            case DATA_CACHE:
                return new DataCacheGenerator(decodeHelper, this);
            //Get data from a data source, such as a server
            case SOURCE:
                return new SourceGenerator(decodeHelper, this);
            case FINISHED:
                return null;
            default:
                throw new IllegalStateException("Unrecognized stage: " + stage);
        }
    }

    private void runGenerators() {
        currentThread = Thread.currentThread();
        startFetchTime = LogTime.getLogTime();
        boolean isStarted = false;
        while (!isCancelled && currentGenerator != null
                   //Here, execute the current Generator and proceed to step 11
                && !(isStarted = currentGenerator.startNext())) {

            stage = getNextStage(stage);
            currentGenerator = getNextGenerator();

            if (stage == Stage.SOURCE) {
                reschedule();
                return;
            }
        }
        // We've run out of stages and generators, give up.
        if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
            notifyFailed();
        }

        // Otherwise a generator started a new load and we expect to be called back in
        // onDataFetcherReady.
    }

currentGenerator.startNext() the currentgenerator here is SourceGenerator

Then look down. SourceGenerator  startNext

    @Override
    public boolean startNext() {
        //The first time you get data from the source data, it will not be executed here
        //It can be seen from the following analysis that the next time there is data, it will also be called here to cache the data to the disk
        if (dataToCache != null) {
            Object data = dataToCache;
            dataToCache = null;
            //Put in cache 
            cacheData(data);
        }

        if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
            return true;
        }
        sourceCacheGenerator = null;

        loadData = null;
        boolean started = false;
        while (!started && hasNextModelLoader()) {
            //helper.getLoadData() gets all qualified modelloaders, including default and custom ones
           // The conditions here are met, that is, whether the handles function in ModelLoader returns true. In addition, the straight white point is to judge whether the object type passed in load() can be processed by ModelLoader
            loadData = helper.getLoadData().get(loadDataListIndex++);
            if (loadData != null
                    && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
                    || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
                started = true;
                //3. Use the fetcher inside the LoadData object to carry out the actual request operation (such as initiating a network request)
                loadData.fetcher.loadData(helper.getPriority(), this);
            }
        }
        return started;
    }
    List<LoadData<?>> getLoadData() {
        if (!isLoadDataSet) {
            isLoadDataSet = true;
            loadData.clear();
            //Get all loaders in the registered loaders that can load the current model
            List<ModelLoader<Object, ?>> modelLoaders = glideContext.getRegistry().getModelLoaders(model);
            //noinspection ForLoopReplaceableByForEach to improve perf
            for (int i = 0, size = modelLoaders.size(); i < size; i++) {
                ModelLoader<Object, ?> modelLoader = modelLoaders.get(i);
                //Each ModeLoader has an internal class LoadData, which is created by the function buildLoadData
                LoadData<?> current =
                        modelLoader.buildLoadData(model, width, height, options);
                if (current != null) {
                    loadData.add(current);
                }
            }
        }
        return loadData;
    }

Now, let's move on to glidecantext getRegistry(). getModelLoaders(model); Here, getregistry () is used to get the {Registry. Next, let's look at how to get LoadData.

View the method getModelLoaders in the # Registry

@NonNull
    public <Model> List<ModelLoader<Model, ?>> getModelLoaders(@NonNull Model model) {
        List<ModelLoader<Model, ?>> result = modelLoaderRegistry.getModelLoaders(model);
        if (result.isEmpty()) {
            throw new NoModelLoaderAvailableException(model);
        }
        return result;
    }
    public <A> List<ModelLoader<A, ?>> getModelLoaders(@NonNull A model) {
        List<ModelLoader<A, ?>> modelLoaders = getModelLoadersForClass(getClass(model));
        int size = modelLoaders.size();
        boolean isEmpty = true;
        List<ModelLoader<A, ?>> filteredLoaders = Collections.emptyList();
        for (int i = 0; i < size; i++) {
            ModelLoader<A, ?> loader = modelLoaders.get(i);
            //Use the ModelLoader#handles method to determine whether the loader can process the current type of data (this data is passed in through load), and return all loaders that can be processed
            if (loader.handles(model)) {
                if (isEmpty) {
                    filteredLoaders = new ArrayList<>(size - i);
                    isEmpty = false;
                }
                filteredLoaders.add(loader);
            }
        }
        return filteredLoaders;
    }

The modelLoader here has a type of StringLoader, StringLoader buildLoadData->MultiModelLoader. buildLoadData

  @Override
  public LoadData<Data> buildLoadData(
      @NonNull Model model, int width, int height, @NonNull Options options) {
    Key sourceKey = null;
    int size = modelLoaders.size();
    List<DataFetcher<Data>> fetchers = new ArrayList<>(size);
    //noinspection ForLoopReplaceableByForEach to improve perf
    for (int i = 0; i < size; i++) {
      //modelLoaders here are assigned when creating a MultiModelLoader
      ModelLoader<Model, Data> modelLoader = modelLoaders.get(i);
      //Here, a filter is carried out to find in a smaller range, which can deal with the ModelLoader of the model. (this time, we are looking for a ModelLoader in StringLoader that can handle URL s)
      if (modelLoader.handles(model)) {
        LoadData<Data> loadData = modelLoader.buildLoadData(model, width, height, options);
        if (loadData != null) {
          //Here, the value of LoadData is extracted,
          sourceKey = loadData.sourceKey;
          //The example here is urlfetcher 
          fetchers.add(loadData.fetcher);
        }
      }
    }
    return !fetchers.isEmpty() && sourceKey != null
        ? new LoadData<>(sourceKey, new MultiFetcher<>(fetchers, exceptionListPool))
        : null;
  }

After obtaining loaddata, {go back to startNext, mark the position 3, and come to loaddata fetcher. loadData(helper.getPriority(), this); The fetcher here is HttpUrlFetcher. Now let's look at the implementation of HttpUrlFetcher loaddata

  @Override
  public void loadData(@NonNull Priority priority,
      @NonNull DataCallback<? super InputStream> callback) {
    long startTime = LogTime.getLogTime();
    try {
      //http request, return an InputStream input stream
      InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
      //Callback InputStream out in the form of callback
      callback.onDataReady(result);
    } catch (IOException e) {
      callback.onLoadFailed(e);
    } finally {
     ...
    }
  }

Let's continue to see how the # loadDataWithRedirects # function generates an # InputStream

  private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl,
      Map<String, String> headers) throws IOException {
    if (redirects >= MAXIMUM_REDIRECTS) {
      throw new HttpException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
    } else {

      try {
        if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
          throw new HttpException("In re-direct loop");
        }
      } catch (URISyntaxException e) {
        // Do nothing, this is best effort.
      }
    }

    urlConnection = connectionFactory.build(url);
    for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
      urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
    }
    urlConnection.setConnectTimeout(timeout);
    urlConnection.setReadTimeout(timeout);
    urlConnection.setUseCaches(false);
    urlConnection.setDoInput(true);

    urlConnection.setInstanceFollowRedirects(false);


    urlConnection.connect();

    stream = urlConnection.getInputStream();
    if (isCancelled) {
      return null;
    }
    final int statusCode = urlConnection.getResponseCode();
    if (isHttpOk(statusCode)) {
      return getStreamForSuccessfulRequest(urlConnection);
    } 
    ...//Let's ignore the abnormality for the time being
  }

Now you can see the Http request. Here is the Http urlconnection as the Glide underlying network request. After the request is successful, an input stream will be returned directly. Finally, it will be called back to the decodejob ondatafetcherread function through onDataReady , and we will return to the , SourceGenerator next time

    @Override
    public void onDataReady(Object data) {

        DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
        if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
          
            dataToCache = data;
            //Calling the reschedule of the DecodeJob to execute the task with the thread pool is actually calling the startNext of the SourceGenerator again
            cb.reschedule();
        } else {
          // Callback is another function
            cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
                    loadData.fetcher.getDataSource(), originalKey);
        }
    }

There will be else here because we have not configured the cache and continue the callback.

 

class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback,
    Runnable,
    Comparable<DecodeJob<?>>,
    Poolable {
      ...
         @Override
  public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
      DataSource dataSource, Key attemptedKey) {
    this.currentSourceKey = sourceKey; //key of the current returned data
    this.currentData = data; //Returned data
    this.currentFetcher = fetcher; //The returned data executor can be understood as HttpUrlFetcher here
    this.currentDataSource = dataSource; //Data source url
    this.currentAttemptingKey = attemptedKey;
    if (Thread.currentThread() != currentThread) {
      runReason = RunReason.DECODE_DATA;
      callback.reschedule(this);
    } else {
      GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData");
      try {
        //4 analyze the returned data
        decodeFromRetrievedData();
      } finally {
        GlideTrace.endSection();
      }
    }
  }   
      ...
      
    }


	//Parse the returned data
  private void decodeFromRetrievedData() {
    Resource<R> resource = null;
    try {
      // Call decodeFrom to parse data; HttpUrlFetcher , InputStream ,  currentDataSource
      resource = decodeFromData(currentFetcher, currentData, currentDataSource);
    } catch (GlideException e) {
      e.setLoggingDetails(currentAttemptingKey, currentDataSource);
      throwables.add(e);
    }
    //Notify after the parsing is completed
    if (resource != null) {
      notifyEncodeAndRelease(resource, currentDataSource);
    } else {
      runGenerators();
    }
  }

Continue with decodeFromData to see how it can be parsed into Resource

 

    private <Data> Resource<R> decodeFromData(DataFetcher<?> fetcher, Data data,
                                              DataSource dataSource) throws GlideException {

        try {
            if (data == null) {
                return null;
            }
            //Continue parsing data
            Resource<R> result = decodeFromFetcher(data, dataSource);
            return result;
        } finally {
            fetcher.cleanup();
        }
    }
    //The data here is a generic type. In this case, it is a Stream stream, which is obtained from an http request
    private <Data> Resource<R> decodeFromFetcher(Data data, DataSource dataSource)
            throws GlideException {
        //Obtain a LoadPath according to the data type (stream here), ResourceDecoder and transcoder
        //It can be seen from these parameters that the final data flow to Bitmap or Drawable is carried out in LoadPath, which refers to DecodePath here
        LoadPath<Data, ?, R> path = decodeHelper.getLoadPath((Class<Data>) data.getClass());
        return runLoadPath(data, dataSource, path);
    }
    private <Data, ResourceType> Resource<R> runLoadPath(Data data, DataSource dataSource,
                                                         LoadPath<Data, ResourceType, R> path) throws GlideException {
        Options options = getOptionsWithHardwareConfig(dataSource);
        DataRewinder<Data> rewinder = glideContext.getRegistry().getRewinder(data);
        try {
            // ResourceType in DecodeCallback below is required for compilation to work with gradle.
            //Execute the load of LoadPath, decode and convert
            return path.load(
                    rewinder, options, width, height, new DecodeCallback<ResourceType>(dataSource));
        } finally {
            rewinder.cleanup();
        }
    }

The above code shows that in order to parse the data, first build a LoadPath, then create a DataRewinder of inputstreamrewender type, and finally put the data parsing operation into LoadPath In the load method, next look at LoadPath Specific logical operation of load method:

    public Resource<Transcode> load(DataRewinder<Data> rewinder, @NonNull Options options, int width,
                                    int height, DecodePath.DecodeCallback<ResourceType> decodeCallback) throws GlideException {
        
        try {
            return loadWithExceptionList(rewinder, options, width, height, decodeCallback, 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;
        //noinspection ForLoopReplaceableByForEach to improve perf
        for (int i = 0, size = decodePaths.size(); i < size; i++) {
            DecodePath<Data, ResourceType, Transcode> path = decodePaths.get(i);
            try {
                //The key point here is to call the decode of LoadPath
                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;
    }

With path decode

  public Resource<Transcode> decode(DataRewinder<DataType> rewinder, int width, int height,
      @NonNull Options options, DecodeCallback<ResourceType> callback) throws GlideException {
    //Call decoderesourcec to parse the data into intermediate resources
    Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options);
    //Call back after parsing data
    Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);
    //Convert resource to target resource
    return transcoder.transcode(transformed, options);
  }
  @NonNull
  private Resource<ResourceType> decodeResource(DataRewinder<DataType> rewinder, int width,
      int height, @NonNull Options options) throws GlideException {
   ...
    try {
      return decodeResourceWithList(rewinder, width, height, options, exceptions);
    } finally {
    ...
    }
  }

  @NonNull
  private Resource<ResourceType> decodeResourceWithList(DataRewinder<DataType> rewinder, int width,
      int height, @NonNull Options options, List<Throwable> exceptions) throws GlideException {
    Resource<ResourceType> result = null;
    //noinspection ForLoopReplaceableByForEach to improve perf
    for (int i = 0, size = decoders.size(); i < size; i++) {
      ResourceDecoder<DataType, ResourceType> decoder = decoders.get(i);
      try {
        DataType data = rewinder.rewindAndGet();
        if (decoder.handles(data, options)) {
          data = rewinder.rewindAndGet();
          // Call resourcedecoder Decode parsing data
          result = decoder.decode(data, width, height, options);
        }

      } catch (IOException | RuntimeException | OutOfMemoryError e) {
				...
      }

    return result;
  }

Decode is a ResourceDecoder interface (resource decoder). It has different implementation classes according to different datatypes and resourcetypes. The implementation class here is ByteBufferBitmapDecoder. Let's take a look at its decode process

  public Resource<Bitmap> decode(@NonNull ByteBuffer source, int width, int height,
      @NonNull Options options)
      throws IOException {
    InputStream is = ByteBufferUtil.toStream(source);
    return downsampler.decode(is, width, height, options);
  }

downsampler is mainly used to decode, rotate, compress and fillet the convection,

    public Resource<Bitmap> decode(InputStream is, int requestedWidth, int requestedHeight,
                                   Options options, DecodeCallbacks callbacks) throws IOException {


        byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
        BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions();
        bitmapFactoryOptions.inTempStorage = bytesForOptions;

        DecodeFormat decodeFormat = options.get(DECODE_FORMAT);
        DownsampleStrategy downsampleStrategy = options.get(DownsampleStrategy.OPTION);
        boolean fixBitmapToRequestedDimensions = options.get(FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS);
        boolean isHardwareConfigAllowed =
                options.get(ALLOW_HARDWARE_CONFIG) != null && options.get(ALLOW_HARDWARE_CONFIG);

        try {
            //Get bitmap by parsing
            Bitmap result = decodeFromWrappedStreams(is, bitmapFactoryOptions,
                    downsampleStrategy, decodeFormat, isHardwareConfigAllowed, requestedWidth,
                    requestedHeight, fixBitmapToRequestedDimensions, callbacks);
             //Encapsulate the bitmap into the Resource and return the Resource object
            return BitmapResource.obtain(result, bitmapPool);
        } finally {
            releaseOptions(bitmapFactoryOptions);
            byteArrayPool.put(bytesForOptions);
        }
    }
  private Bitmap decodeFromWrappedStreams(
      ImageReader imageReader,
      BitmapFactory.Options options,
      DownsampleStrategy downsampleStrategy,
      DecodeFormat decodeFormat,
      PreferredColorSpace preferredColorSpace,
      boolean isHardwareConfigAllowed,
      int requestedWidth,
      int requestedHeight,
      boolean fixBitmapToRequestedDimensions,
      DecodeCallbacks callbacks)
      throws IOException {
    long startTime = LogTime.getLogTime();

    int[] sourceDimensions = getDimensions(imageReader, options, callbacks, bitmapPool);
    int sourceWidth = sourceDimensions[0];
    int sourceHeight = sourceDimensions[1];
    String sourceMimeType = options.outMimeType;

    // If we failed to obtain the image dimensions, we may end up with an incorrectly sized Bitmap,
    // so we want to use a mutable Bitmap type. One way this can happen is if the image header is so
    // large (10mb+) that our attempt to use inJustDecodeBounds fails and we're forced to decode the
    // full size image.
    if (sourceWidth == -1 || sourceHeight == -1) {
      isHardwareConfigAllowed = false;
    }

    int orientation = imageReader.getImageOrientation();
    int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation);
    boolean isExifOrientationRequired = TransformationUtils.isExifOrientationRequired(orientation);

    int targetWidth =
        requestedWidth == Target.SIZE_ORIGINAL
            ? (isRotationRequired(degreesToRotate) ? sourceHeight : sourceWidth)
            : requestedWidth;
    int targetHeight =
        requestedHeight == Target.SIZE_ORIGINAL
            ? (isRotationRequired(degreesToRotate) ? sourceWidth : sourceHeight)
            : requestedHeight;

    ImageType imageType = imageReader.getImageType();

    //Calculate the scale, and the result will be reflected in the options parameter
    calculateScaling(
        imageType,
        imageReader,
        callbacks,
        bitmapPool,
        downsampleStrategy,
        degreesToRotate,
        sourceWidth,
        sourceHeight,
        targetWidth,
        targetHeight,
        options);
        
    calculateConfig(
        imageReader,
        decodeFormat,
        isHardwareConfigAllowed,
        isExifOrientationRequired,
        options,
        targetWidth,
        targetHeight);
	//Calculate whether the sdk version is greater than KITKAT. In this system and before, image multiplexing only supports bitmaps of the same size 
    boolean isKitKatOrGreater = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
    //The following judgment is used to calculate whether there is a bitmap in BitmapPool that can be reused. If so, set the bitmap to options inBitmap
    //In this way, when parsing and generating bitmap according to options, there is no need to allocate memory again to avoid memory jitter
    // Prior to KitKat, the inBitmap size must exactly match the size of the bitmap we're decoding.
    if ((options.inSampleSize == 1 || isKitKatOrGreater) && shouldUsePool(imageType)) {
      int expectedWidth;
      int expectedHeight;
      if (sourceWidth >= 0
          && sourceHeight >= 0
          && fixBitmapToRequestedDimensions
          && isKitKatOrGreater) {
        expectedWidth = targetWidth;
        expectedHeight = targetHeight;
      } else {
        float densityMultiplier =
            isScaling(options) ? (float) options.inTargetDensity / options.inDensity : 1f;
        int sampleSize = options.inSampleSize;
        int downsampledWidth = (int) Math.ceil(sourceWidth / (float) sampleSize);
        int downsampledHeight = (int) Math.ceil(sourceHeight / (float) sampleSize);
        expectedWidth = Math.round(downsampledWidth * densityMultiplier);
        expectedHeight = Math.round(downsampledHeight * densityMultiplier);

      }
      // If this isn't an image, or BitmapFactory was unable to parse the size, width and height
      // will be -1 here.
      if (expectedWidth > 0 && expectedHeight > 0) {
        //This function will find the bitmap matching the size in the bitmapPool, and set it to inBitmap if found
        setInBitmap(options, bitmapPool, expectedWidth, expectedHeight);
      }
    }

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
      boolean isP3Eligible =
          preferredColorSpace == PreferredColorSpace.DISPLAY_P3
              && options.outColorSpace != null
              && options.outColorSpace.isWideGamut();
      options.inPreferredColorSpace =
          ColorSpace.get(isP3Eligible ? ColorSpace.Named.DISPLAY_P3 : ColorSpace.Named.SRGB);
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      options.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
    }
	//Parse the stream into Bitmap according to options
    Bitmap downsampled = decodeStream(imageReader, options, callbacks, bitmapPool);
    callbacks.onDecodeComplete(bitmapPool, downsampled);

    Bitmap rotated = null;
    if (downsampled != null) {
      // If we scaled, the Bitmap density will be our inTargetDensity. Here we correct it back to
      // the expected density dpi.
      downsampled.setDensity(displayMetrics.densityDpi);

      rotated = TransformationUtils.rotateImageExif(bitmapPool, downsampled, orientation);
      if (!downsampled.equals(rotated)) {
        bitmapPool.put(downsampled);
      }
    }

    return rotated;
  }

So far, the resolved resources have been obtained. The next step is to display them on the specified ImageView control

Go back to the comment in the above method 4. Decodejob , notifyEncodeAndRelease(resource, currentDataSource);

private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {

        if (resource instanceof Initializable) {
            ((Initializable) resource).initialize();
        }

        Resource<R> result = resource;
        LockedResource<R> lockedResource = null;
        if (deferredEncodeManager.hasResourceToEncode()) {
            lockedResource = LockedResource.obtain(resource);
            result = lockedResource;
        }
        // Notify the main thread to call back and load the picture
        notifyComplete(result, dataSource);
        // Update status is code
        stage = Stage.ENCODE;
        try {
            //Can I cache the converted pictures
            if (deferredEncodeManager.hasResourceToEncode()) {
                //Disk cache entry
                deferredEncodeManager.encode(diskCacheProvider, options);
            }
        } finally {
            if (lockedResource != null) {
                lockedResource.unlock();
            }
        }
        // Call onEncodeComplete outside the finally block so that it's not called if the encode process
        // throws.
        onEncodeComplete();
    }
    private void notifyComplete(Resource<R> resource, DataSource dataSource) {
        setNotifiedOrThrow();
         // This callback is the EngineJob object, which is passed in when creating a Decodejob. See Step 9 in this section
        callback.onResourceReady(resource, dataSource);
    }

Let's look directly at the onResourceReady callback function of EngineJob

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


  @Synthetic
  void notifyCallbacksOfResult() {
    ResourceCallbacksAndExecutors copy;
    Key localKey;
    EngineResource<?> localResource;
    synchronized (this) {
      stateVerifier.throwIfRecycled();
      if (isCancelled) {
        resource.recycle();
        release();
        return;
      } else if (cbs.isEmpty()) {
      ... 
      }
      engineResource = engineResourceFactory.build(resource, isCacheable);
      hasResource = true;
      copy = cbs.copy();
      incrementPendingCallbacks(copy.size() + 1);

      localKey = key;
      localResource = engineResource;
    }

    //The callback of the upper Engine task is completed
    listener.onEngineJobComplete(this, localKey, localResource);

    //Traversal resource callback to ImageViewTarget 
    for (final ResourceCallbackAndExecutor entry : copy) {
      entry.executor.execute(new CallResourceReady(entry.cb));
    }
    decrementPendingCallbacks();
  }

Through the onResourceReady callback function of EngineJob above, two main things are done

  1. Notify the upper layer of the completion of the task.
  2. The callback ImageViewTarget is used to display data.

Let's take a look at listener Concrete implementation of onenginejobcomplete

  @SuppressWarnings("unchecked")
  @Override
  public synchronized void onEngineJobComplete(
      EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
    if (resource != null) {
      resource.setResourceListener(key, this);
			//The resources returned from the downstream are received and added to the active cache
      if (resource.isCacheable()) {
        activeResources.activate(key, resource);
      }
    }

    jobs.removeIfCurrent(key, engineJob);
  }

After notifying ImageViewTarget, let's look at the operation in the EngineJob method , traversing the resource callback to ImageViewTarget ,

//Traversal resource callback to ImageViewTarget 
    for (final ResourceCallbackAndExecutor entry : copy) {
      entry.executor.execute(new CallResourceReady(entry.cb));
    }
  private class CallResourceReady implements Runnable {

    private final ResourceCallback cb;

    CallResourceReady(ResourceCallback cb) {
      this.cb = cb;
    }

    @Override
    public void run() {
      synchronized (EngineJob.this) {
        if (cbs.contains(cb)) {
         ...
          //Return prepared resources
          callCallbackOnResourceReady(cb);
          removeCallback(cb);
        }
        decrementPendingCallbacks();
      }
    }
  }

Finally, let's continue with callcallcallbackonresourceready

  @Synthetic
  synchronized void callCallbackOnResourceReady(ResourceCallback cb) {
    try {
      //Callback to SingleRequest
      cb.onResourceReady(engineResource, dataSource);
    } catch (Throwable t) {
      throw new CallbackException(t);
    }
  }


Finally, take a look at the SingleRequest onResourceReady callback

 public synchronized void onResourceReady(Resource<?> resource, DataSource dataSource) {
    stateVerifier.throwIfRecycled();
    loadStatus = null;
   
  ....
		
    Object received = resource.get();
    if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) {
      releaseResource(resource);

      ...
        
      onLoadFailed(exception);
      return;
    }

    if (!canSetResource()) {
      releaseResource(resource);
      status = Status.COMPLETE;
      return;
    }

   //When resources are ready
    onResourceReady((Resource<R>) resource, (R) received, dataSource);
  }


private synchronized void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {
   
  ...

      anyListenerHandledUpdatingTarget |=
          targetListener != null
              && targetListener.onResourceReady(result, model, target, dataSource, isFirstResource);

      if (!anyListenerHandledUpdatingTarget) {
        Transition<? super R> animation =
            animationFactory.build(dataSource, isFirstResource);
        //The callback to the target ImageViewTarget resource is ready
        target.onResourceReady(result, animation);
      }
    } finally {
      isCallingCallbacks = false;
    }

  	//Loading succeeded
    notifyLoadSuccess();
  }

public abstract class ImageViewTarget<Z> extends ViewTarget<ImageView, Z>
    implements Transition.ViewAdapter { 
  
  ...
    


  private void setResourceInternal(@Nullable Z resource) {
    //Call setResource function to display the resource
    setResource(resource);
    ...
  }
}

Here, the specific display methods in BitmapImageViewTarget and DrawableImageViewTarget in the implementation class of ImageViewTarget are as follows

public class DrawableImageViewTarget extends ImageViewTarget<Drawable> {

  public DrawableImageViewTarget(ImageView view) {
    super(view);
  }

  // Public API.
  @SuppressWarnings({"unused", "deprecation"})
  @Deprecated
  public DrawableImageViewTarget(ImageView view, boolean waitForLayout) {
    super(view, waitForLayout);
  }

  @Override
  protected void setResource(@Nullable Drawable resource) {
    view.setImageDrawable(resource);
  }
}
public class BitmapImageViewTarget extends ImageViewTarget<Bitmap> {
  
  @SuppressWarnings("WeakerAccess")
  public BitmapImageViewTarget(ImageView view) {
    super(view);
  }

  
  @SuppressWarnings({"unused", "deprecation"})
  @Deprecated
  public BitmapImageViewTarget(ImageView view, boolean waitForLayout) {
    super(view, waitForLayout);
  }

  
  @Override
  protected void setResource(Bitmap resource) {
    view.setImageBitmap(resource);
  }
}

The picture information is displayed through the setrestore method.

So far, the Glide source code has been roughly analyzed

Summary

Generally speaking, the picture loading process can be roughly divided into four steps:

  1. preparation
  2. Memory cache
  3. Data loading
  4. Decoding display

Preparations include:

  1. Add a Fragment to the corresponding Activity/Fragment to listen to the life cycle. To control the suspension, resumption, or resource cleanup of the request
  2. Recursively construct the Request. To coordinate the loading requests of main diagram, thumbnail and error diagram. The thumbnail and the main image are loaded at the same time. If the main image is loaded first, the thumbnail loading Request will be cancelled. Otherwise, the thumbnail will be displayed first and cleared after the main image is loaded. The error diagram is loaded and displayed only after the main diagram fails to load
  3. Get the target size. Taking ImageView as an example, the size is obtained after the onPreDraw method callback of ViewTreeObserver.
  4. Set the percentage bitmap. The occupation bitmap is set by directly calling the setImageDrawable method, so it does not need to be coordinated with the main graph

The memory cache consists of two parts:

  1. Active resources, that is, resources that have been recalled to the Target after loading and have not been released, can be simply understood as the picture being displayed, and the specific release time can be determined by reference counting
  2. Memory cache, that is, resources that are no longer displayed but are still in memory and have not been removed by LRU algorithm

Data loading includes two parts:

  1. Load from disk cache
  2. Load from destination address

Disk cache is divided into two parts:

  1. Find from the scaled and transformed file cache
  2. If not, go to the original image cache to find it

There are many kinds of target addresses, such as local file path, file handle, Asset, server link, etc.

After successfully obtaining data:

  1. First, you need to cache the original image data to the local disk
  2. Then decode the data into resource types such as Bitmap/Drawable, and apply transformation effects such as fillet (if not obtained from the transformed file cache)
  3. Then it can be called back to the Target and displayed
  4. Finally, you need to write the scaled and transformed image data to the disk cache and clean up resources

When the Target displays the picture, it will also judge whether the transition animation needs to be executed. If necessary, the resource will be displayed after the transition animation. When displaying a resource, it will judge whether the resource type is a gif diagram. If so, start the animation, otherwise it can be displayed directly.