preface

Since the previous project was built with MVP architecture, which is a combination of RxJava + Glide + OKHttp + Retrofit and other open source frameworks, it was just on the use level without in-depth research. Recently, we plan to attack all of them. Students who have not paid attention to them can pay attention to a wave first. After reading this series of articles, you’ll be much more comfortable handling questions (whether in an interview or on the job) if you know how they work.

Android picture loading framework Glide 4.9.0 (a) From the perspective of source code analysis Glide execution process

Android Image loading framework Glide 4.9.0 (2) Analysis of Glide cache strategy from the perspective of source code

From the perspective of source code analysis of Rxjava2 basic execution process, thread switching principle

OKHttp3 (a) synchronous and asynchronous execution process is analyzed from the perspective of source code

Analyze the charm of OKHttp3 (ii) interceptor from the source point of view

OKHttp3 (iii) cache strategy is analyzed from a source code perspective

Analyze Retrofit network request from source code point of view, including RxJava + Retrofit + OKhttp network request execution process

An overview of the

I believe that everyone in the project should be useful or know Glide picture loading framework, so in the use of time is not found a line of Glide code can download the picture -> cache -> display, so Glide inside it exactly how to achieve? The following we will analyze the Glide execution process, head there is a general understanding of Glide source code execution process, we will start from the following the most simple code analysis:

Glide.with(Activity activity).load(String url).into(ImageView imageView);
Copy the code

This simple line of code is extremely complex inside.

Ps: The recommended reading time is 30 minutes, I believe you will have a harvest after reading.

with

Before analyzing the source code, let’s have a look at the sequence diagram of Glide. With execution, first have an understanding of the execution process, we are to analyze the code to do what?

The above is only Glide. With (Activity Act) process, so these classes involved above, what are their responsibilities, let’s have a look.

  • Glide
    • Do some init work, such as caching, thread pool, reuse pool construction, etc.
  • RequestManagerRetriever
    • The main thing is getting oneRequestManagerRequest management class, then bind a Fragment.
  • SupportRequestManagerFragment :
    • Used to manage the life cycle of requests.
  • RequestManager
    • Mainly used for administrative encapsulation of requests.

Process and each role we introduced, now introduce the code concrete implementation;

  1. Call with

     Glide.with(Activity);
    Copy the code

    If we look at the source function with, there are a lot of 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);
        }
    Copy the code

    In fact, there are three commonly used forms of Activity, Fragment, and Context. Here we will use Activity as the main form.

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

    Continue to look at the get (context)

        @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;
        }
    Copy the code

    Glide Get (Context) is a double detection singleton mode (DCL), to ensure the safety of multithreading, if the singleton mode is not clear can see between me to write a singleton design mode details, So let’s look at checkAndInitializeGlide(Context); What did it do?

        private static void checkAndInitializeGlide(@NonNull Context context) {
            if (isInitializing) {
                // A message is thrown to initialize the exception
            } else {
              	// Whether to initialize flags
                isInitializing = true;
              	// Start initialization
                initializeGlide(context);
                isInitializing = false; }}Copy the code

    Continue to see initializeGlide (context);

     private static void initializeGlide(@NonNull Context context) {
          // instantiate a GlideBuilder for initialization
       		// Some default configuration information for GlideBuilder
       		initializeGlide(context, new GlideBuilder());
        }
    Copy the code

    Continue to follow

        private static void initializeGlide(@NonNull Context context, @NonNull GlideBuilder builder) {
          	//1. Get the context at the application level, which can avoid memory leaks, and we can also get the context in this way in actual development.
            Context applicationContext = context.getApplicationContext();
          	/ / 2. Here to get the @ the annotation processor generated GeneratedAppGlideModuleImpl GlideModule logo, GeneratedAppGlideModuleFactory... And so on.GeneratedAppGlideModule annotationGeneratedModule = getAnnotationGeneratedGlideModules(); .//3. Get the code generated by the annotations to RequestManagerFactoryRequestManagerFactory factory = annotationGeneratedModule ! =null ? annotationGeneratedModule.getRequestManagerFactory() : null;
          	//4. Add the factory to GlideBuilderbuilder.setRequestManagerFactory(factory); .//5. Glide instance object is built through Builder mode
            Glide glide = builder.build(applicationContext);
            Iterator var13 = manifestModules.iterator();
    
          	//6. Start registering component callback
            while(var13.hasNext()) {
                GlideModule module = (GlideModule)var13.next();
                module.registerComponents(applicationContext, glide, glide.registry);
            }
    
            if(annotationGeneratedModule ! =null) {
               annotationGeneratedModule.registerComponents(applicationContext, glide, glide.registry);
            }
    				
            applicationContext.registerComponentCallbacks(glide);
          	// Assign the built Glide to the static variable of glide
             Glide.glide = glide;
        }
    Copy the code

    I’m sure it’s easy for you to understand from the comments above, but look at comment 5, where we know that it’s generated by the builder, so let’s see how it’s implemented internally.

    package com.bumptech.glide;
    
    
    /** * A builder class for setting default structural classes for Glide to use. */
    public final class GlideBuilder {
    
      // Manage thread pools
      private Engine engine;
      // Object pool (share mode), this avoids creating objects repeatedly, which has some effect on memory overhead
      private BitmapPool bitmapPool;
      private ArrayPool arrayPool;
     
      / / GlideExecutor thread pool
      private GlideExecutor sourceExecutor;
      private GlideExecutor diskCacheExecutor;
      // Local disk cache
      private DiskCache.Factory diskCacheFactory;
      // Memory cache
      private MemorySizeCalculator memorySizeCalculator;
      private MemoryCache memoryCache;
      private ConnectivityMonitorFactory connectivityMonitorFactory;
      private int logLevel = Log.INFO;
      private RequestOptions defaultRequestOptions = new RequestOptions();
      @Nullable
      private RequestManagerFactory requestManagerFactory;
      private GlideExecutor animationExecutor;
      private boolean isActiveResourceRetentionAllowed;
      @Nullable
      private List<RequestListener<Object>> defaultRequestListeners;
      private boolean isLoggingRequestOriginsEnabled;
    
      // All configuration information, using the open and close principle..// Start building
      @NonNull
      Glide build(@NonNull Context context) {
        // Instantiate a thread pool for a network request
        if (sourceExecutor == null) {
          sourceExecutor = GlideExecutor.newSourceExecutor();
        }
    		// Instantiate a thread pool for the local disk cache
        if (diskCacheExecutor == null) {
          diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();
        }
    		// instantiate a thread pool for loading picture animations
        if (animationExecutor == null) {
          animationExecutor = GlideExecutor.newAnimationExecutor();
        }
    		// instantiate a calculation of images loaded into memory
        if (memorySizeCalculator == null) {
          memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();
        }
    		// Instantiate a factory for default network connection monitoring
        if (connectivityMonitorFactory == null) {
          connectivityMonitorFactory = new DefaultConnectivityMonitorFactory();
        }
    		
        // Instantiate a Bitmap object pool
        if (bitmapPool == null) {
          int size = memorySizeCalculator.getBitmapPoolSize();
          // If there is any available in the pool, add it directly to the least recently used LruBitmap container
          if (size > 0) {
            bitmapPool = new LruBitmapPool(size);
          } else {
            // If the pool is full, the BitmapPoolAdapter is installed
            bitmapPool = newBitmapPoolAdapter(); }}// instantiate an array object pool
        if (arrayPool == null) {
          arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes());
        }
    		
        // Resource memory cache
        if (memoryCache == null) {
          memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
        }
    		// The factory for disk caching
        if (diskCacheFactory == null) {
          diskCacheFactory = new InternalCacheDiskCacheFactory(context);
        }
    		
        // Build an engine that implements caching policies and thread pools
        if (engine == null) {
          engine =
              new Engine(
                  memoryCache,
                  diskCacheFactory,
                  diskCacheExecutor,
                  sourceExecutor,
                  GlideExecutor.newUnlimitedSourceExecutor(),
                  GlideExecutor.newAnimationExecutor(),
                  isActiveResourceRetentionAllowed);
        }
    
        if (defaultRequestListeners == null) {
          defaultRequestListeners = Collections.emptyList();
        } else {
          defaultRequestListeners = Collections.unmodifiableList(defaultRequestListeners);
        }
    		// Instantiate a RequestManagerRetriever request management class
        RequestManagerRetriever requestManagerRetriever =
            new RequestManagerRetriever(requestManagerFactory);
    
        // Instantiate Glide
        return newGlide( context, engine, memoryCache, bitmapPool, arrayPool, requestManagerRetriever, connectivityMonitorFactory, logLevel, defaultRequestOptions.lock(), defaultTransitionOptions, defaultRequestListeners, isLoggingRequestOriginsEnabled); }}Copy the code

    Build a thread pool, a reuse pool, a cache, a cache, and a Glide instance.

    Glide(
          @NonNull Context context,
          @NonNull Engine engine,
          @NonNull MemoryCache memoryCache,
          @NonNull BitmapPool bitmapPool,
          @NonNull ArrayPool arrayPool,
          @NonNull RequestManagerRetriever requestManagerRetriever,
          @NonNull ConnectivityMonitorFactory connectivityMonitorFactory,
          int logLevel,
          @NonNull RequestOptions defaultRequestOptions,
          @NonNullMap<Class<? >, TransitionOptions<? ,? >> defaultTransitionOptions,@NonNull List<RequestListener<Object>> defaultRequestListeners,
          boolean isLoggingRequestOriginsEnabled) {
      	// Save the thread pool, object pool, cache pool built by Builder to Glide
        this.engine = engine;
        this.bitmapPool = bitmapPool;
        this.arrayPool = arrayPool;
        this.memoryCache = memoryCache;
        this.requestManagerRetriever = requestManagerRetriever;
        this.connectivityMonitorFactory = connectivityMonitorFactory;
    		
      	// Get Glide required codec
        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());
    	
      // Ignore some configuration information.// The factory used to display the corresponding image
        ImageViewTargetFactory imageViewTargetFactory = new ImageViewTargetFactory();
      
      // Build a Glide specific context
        glideContext =
            new GlideContext(
                context,
                arrayPool,
                registry,
                imageViewTargetFactory,
                defaultRequestOptions,
                defaultTransitionOptions,
                defaultRequestListeners,
                engine,
                isLoggingRequestOriginsEnabled,
                logLevel);
      }
    Copy the code

    Here GlideContext is the Context at the Context level.

    public class GlideContext extends ContextWrapper{... }Copy the code

    Now that we know how to build the cache policy, Glide, GlideContext, let’s see how to get the RequestManager class

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

    Here get also has a lot of overloaded functions, let’s look at the overloaded Activity parameter.

    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 not executed for an application-level Context
        } else if(Util.isOnMainThread() && ! (contextinstanceof Application)) {
          if (context instanceof FragmentActivity) {
            return get((FragmentActivity) context);
          } else if (context instanceof Activity) {
            return get((Activity) context);
          } else if (context instanceof ContextWrapper) {
            // all the way to BaseContext
            returnget(((ContextWrapper) context).getBaseContext()); }}// Execute directly if not in the main thread or if it is Application
        return getApplicationManager(context);
      }
    
      @NonNull
      public RequestManager get(@NonNull FragmentActivity activity) {... }@NonNull
      public RequestManager get(@NonNull Fragment fragment) {... }// Get the RequestManager from the Activity
      @SuppressWarnings("deprecation")
      @NonNull
      public RequestManager get(@NonNull Activity activity) {
        // Determine whether a task is currently requested in a child thread
        if (Util.isOnBackgroundThread()) {
          // Load through the 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();
          // Generate a Fragment and bind it to a RequestManager
          return fragmentGet(
              activity, fm, /*parentHint=*/ null, isActivityVisible(activity)); }}}Copy the code

    Let’s look at the fragmentGet implementation

      private RequestManager fragmentGet(@NonNull Context context,
          @NonNull android.app.FragmentManager fm,
          @Nullable android.app.Fragment parentHint,
          boolean isParentVisible) {
        //1. Add a Fragment on the current Acitivty to manage the request life cycle
        RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
        // Get the admin class for the current request
        RequestManager requestManager = current.getRequestManager();
        // If not, create a request manager to remain in the Fragment for the current management life cycle, equivalent to binding the two to avoid memory leaks.
        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;
      }
    Copy the code

    As you can see from the code above, this is used to manage the life cycle of a Fragment request, so let’s look at how a Fragment can be added to an Activity.

      private RequestManagerFragment getRequestManagerFragment(
          @NonNull final android.app.FragmentManager fm,
          @Nullable android.app.Fragment parentHint,
          boolean isParentVisible) {
        // Get an instantiated Fragment with a TAG, equivalent to 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 have a Fragment in your current Activity that manages the request lifecycle, check it from the cache
        if (current == null) {
          current = pendingRequestManagerFragments.get(fm);
          // Instantiate a Fragment if the cache does not have one
          if (current == null) {
            current = new RequestManagerFragment();
            current.setParentFragmentHint(parentHint);
            // Start if there is already a request for execution
            if (isParentVisible) {
              current.getGlideLifecycle().onStart();
            }
            // Add to the Map cache
            pendingRequestManagerFragments.put(fm, current);
            // Add a Fragment container from the current Activity's FragmentManager
            fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
            // Add to FragmentManager successfully, send clear cache.handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget(); }}return current;
      }
    Copy the code

    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 not, create a request manager to remain in the Fragment for the current management life cycle, equivalent to binding the two to avoid memory leaks.
        if (requestManager == null) {
          // Get the single Glide
          Glide glide = Glide.get(context);
          / / build request management, current getGlideLifecycle (), we'll talk about this class is ActivityFragmentLifecycle behind
          requestManager =
              factory.build(
                  glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
          // Bind the constructed request management to the Fragment.
          current.setRequestManager(requestManager);
        }
        // Returns the manager of the current request
        return requestManager;
      }
    Copy the code

    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) {
          / / instantiate
          return newRequestManager(glide, lifecycle, requestManagerTreeNode, context); }};Copy the code
    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));
    
        // Add life cycle listener, Fragment pass
        if (Util.isOnBackgroundThread()) {
          mainHandler.post(addSelfToLifecycle);
        } else {
          lifecycle.addListener(this);
        }
        // Add a listener for network changes
        lifecycle.addListener(connectivityMonitor);
    
        defaultRequestListeners =
            new CopyOnWriteArrayList<>(glide.getGlideContext().getDefaultRequestListeners());
        setRequestOptions(glide.getGlideContext().getDefaultRequestOptions());
    		
        glide.registerRequestManager(this);
      }
    Copy the code

    The RequestManager + Fragment class has been bound successfully, and the declaration cycle listening has been set.

    So how do they keep each other alive? We’re looking at the Fragment life cycle method

    // Monitor the life cycle of a Fragment. // Monitor the life cycle of an Activity. // Monitor the life cycle of an Activity.
    public class RequestManagerFragment extends Fragment {
      
      // Equivalent to a lifecycle callback
    	private finalActivityFragmentLifecycle lifecycle; .@Override
      public void onStart(a) {
        super.onStart();
        lifecycle.onStart();
      }
    
      @Override
      public void onStop(a) {
        super.onStop();
        lifecycle.onStop();
      }
    
      @Override
      public void onDestroy(a) {
        super.onDestroy(); lifecycle.onDestroy(); }... }Copy the code

    What is Lifecycle here that we are going to take a look at

    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(a) {
        isStarted = true;
        for(LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) { lifecycleListener.onStart(); }}void onStop(a) {
        isStarted = false;
        for(LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) { lifecycleListener.onStop(); }}void onDestroy(a) {
        isDestroyed = true;
        for(LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) { lifecycleListener.onDestroy(); }}}Copy the code

    It implements the Lifecycle Lifecycle interface in Glide, registers the RequestManager that was instantiated in the RequestManagerFactory we just explained and then adds Lifecycle callback listeners to the constructor, Let’s see.

    public class RequestManager implements LifecycleListener.ModelTypes<RequestBuilder<Drawable>> {...@Override
      public synchronized void onStart(a) {
        resumeRequests();
        targetTracker.onStart();
      }
    
    
      @Override
      public synchronized void onStop(a) {
        pauseRequests();
        targetTracker.onStop();
      }
    
    
      @Override
      public synchronized void onDestroy(a) {
        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); }... }Copy the code

    These three callbacks are passed by the Fragment and are used to monitor the status of the request in real time.

    With the conclusion:

    According to the source code analysis, we know that Glide. With (Activity) mainly do thread pool + cache + request management and life cycle binding + other configuration initialization construction, the internal code is actually very large, everywhere is the design mode, If you don’t know anything about design patterns, check out the design patterns article I wrote earlier. Glide. With our analysis done, what does the load function mainly do?

load

Here we take load(String URL) network image address as an example to explain the load process.

 Glide.with(this).load("Https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3638429004, & FM = 26 & gp = 0. 1717840478 JPG")
Copy the code

As usual, a sequence diagram first.

  • RequestBuilder: this is a common RequestBuilder class that handles setup options and startup loads for common resource types.

The load function is relatively easy to load. Let’s look at the actual code implementation

public class RequestManager implements LifecycleListener.ModelTypes<RequestBuilder<Drawable>> {...public RequestBuilder<Drawable> load(@Nullable String string) {
    // Here we call the Drawable image load requester to load it
    return asDrawable().load(string);
  }
  
  public RequestBuilder<Drawable> asDrawable(a) {
    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
       
         resourceClass)
        {
    return new RequestBuilder<>(glide, this, resourceClass, context); }}Copy the code

You can see that load also has a lot of overloaded functions, but it’s really powerful, just give an image resource and it’ll load it 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 - here it can be seen as the http://xxxx.png we just passed in
  @Nullable private Object model;
  // Describe whether the request has added a loaded data source
  private boolean isModelSet;
  
  private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
    this.model = model;
    isModelSet = true;
    return this; }}Copy the code

Ok, now that the RequestBuilder is built, it’s time to wait for the request to be executed. Let’s look at its RequestBuilder into method.

into

We have finally got into the main character, everyone can’t wait, so now let’s follow into together, as usual, first up a sequence diagram.

After 2 hours of work, the into sequence diagram was finally drawn. If you feel unclear, you can download the original version. There are too many classes and callbacks involved.

Glide.with(activity).load(http://xxxx.png).into(imageView);
Copy the code
  @NonNull
  public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
    Util.assertMainThread();
    Preconditions.checkNotNull(view);
    Rebuild requestOptions based on the scaleType in the ImageView layoutBaseRequestOptions<? > requestOptions =this;
    if(! requestOptions.isTransformationSet() && requestOptions.isTransformationAllowed() && view.getScaleType() ! =null) {
      // If scaleType is not set in the XML ImageView node, it is initialized in the constructor by default to mScaleType = scaletype.fit_center;
      switch (view.getScaleType()) {
       .....
        case FIT_CENTER:
        case FIT_START:
        case FIT_END:
          // Clone (prototype design mode) is used here, and choose a solution that is properly centered for display
          requestOptions = requestOptions.clone().optionalFitCenter();
          break; . }}// Call into overload to create a ViewTarget
    return into(
      	// Call buildImageViewTarget to build a Target of type ImageView (Bitmap/Drawable)
        glideContext.buildImageViewTarget(view, transcodeClass),
        /*targetListener=*/ null,
        requestOptions,
        Executors.mainThreadExecutor());
  }
Copy the code

According to the above code and comments:

  1. Get the current ImageView getScaleType property and re-clone it.
  2. Call the into overload to continue building;

Let’s look at glideContext buildImageViewTarget is how to build up ImageViewTarget

  @NonNull
  public <X> ViewTarget<ImageView, X> buildImageViewTarget( @NonNull ImageView imageView, @NonNull Class
       
         transcodeClass)
        {
    // Call the factory schema to generate a corresponding ImageViewTarget based on transcodeClass
    return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
  }
Copy the code
public class ImageViewTargetFactory {
  @NonNull
  @SuppressWarnings("unchecked")
  public <Z> ViewTarget<ImageView, Z> buildTarget(@NonNull ImageView view, @NonNull Class
       
         clazz)
        {
    // Create a Bitmap ImageViewTarget if the encoding type of the target is Bitmap
    if (Bitmap.class.equals(clazz)) {
      return (ViewTarget<ImageView, Z>) new BitmapImageViewTarget(view);
    //// Create an ImageViewTarget of Drawable if the encoding type of the target is Drawable
    } else if (Drawable.class.isAssignableFrom(clazz)) {
      return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);
    } else {
      throw new IllegalArgumentException(
          "Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)"); }}}Copy the code

Note that BitmapImageViewTarget is produced only when asBitmap is called, so let’s focus on Drawable. Let’s take a quick look at how this Target is implemented internally. Because we’ll get to that at the end, just to give you an idea.

public class DrawableImageViewTarget extends ImageViewTarget<Drawable> {

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

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

  @Override
  protected void setResource(@Nullable Drawable resource) { view.setImageDrawable(resource); }}Copy the code

DrawableImageViewTarget inherits the setResource function overwritten by ImageViewTarget, which implements the logic for displaying a Drawable image. Continue into reload.

  private <Y extends Target<TranscodeType>> Y into( @NonNull Y target, @Nullable RequestListener
       
         targetListener, BaseRequestOptions
         options, Executor callbackExecutor)
        {
    Preconditions.checkNotNull(target);
    // isModelSet is set to true during load, so no exceptions are thrown
    if(! isModelSet) {throw new IllegalArgumentException("You must call #load() before calling #into()");
    }
		// Generate a Glide Request request for this http://xxx.png
    Request request = buildRequest(target, targetListener, options, callbackExecutor);
		// Get the last request
    Request previous = target.getRequest();
    // The following lines indicate whether the request conflicts with the previous one
    if(request.isEquivalentTo(previous) && ! isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) { request.recycle();if(! Preconditions.checkNotNull(previous).isRunning()) { previous.begin(); }return target;
    }
		// Clear up the target request management
    requestManager.clear(target);
    // Re-set a Glide Request for the target
    target.setRequest(request);
    // Finally, the track of the RequestManager is called to execute the Glide Request of the target
    requestManager.track(target, request);

    return target;
  }
Copy the code

Here are two important points:

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

Let’s take a quick look at how to construct a Request. Okay

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

Continue to follow

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

Finally, SingleRequest. Obtain builds the Request object for us, starting with the initialization of some configuration properties. Let’s look at the track function execution where begin begins.

// Add a synchronization lock to the current class to avoid thread-induced security
synchronized void track(@NonNull Target
        target, @NonNull Request request) {
  	// Add a target task
  	targetTracker.track(target);
  	// Execute Glide Request
    requestTracker.runRequest(request);
  }
Copy the code
  public void runRequest(@NonNull Request request) {
    // Add a request
    requests.add(request);
    // Whether to pause
    if(! isPaused) {// Without pausing, Request BEGIN is invoked
      request.begin();
    } else {
      // If pause is called, clean up the requestrequest.clear(); pendingRequests.add(request); }}Copy the code

The logic is to add a request for Requests, see if it is stopped, and call Request.begin () if not; The execution.

So Request is an interface, and the buildRequest function that we talked about earlier is called SingleRequest so let’s just look at its begin function.

  @Override
  public synchronized void begin(a) {
    assertNotCallingCallbacks();
    stateVerifier.throwIfRecycled();
    startTime = LogTime.getLogTime();
    if (model == null) {
      // Check that the size of the external call is valid
      if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        width = overrideWidth;
        height = overrideHeight;
      }
      // Failed callback
      int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
      onLoadFailed(new GlideException("Received null model"), logLevel);
      return;
    }
    if (status == Status.RUNNING) {
      throw new IllegalArgumentException("Cannot restart a running request");
    }

    if (status == Status.COMPLETE) {
      // Indicates that the resource is ready
      onResourceReady(resource, DataSource.MEMORY_CACHE);
      return;
    }


    status = Status.WAITING_FOR_SIZE;
    // This indicates that the size is ready
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
      / /
      onSizeReady(overrideWidth, overrideHeight);
    } else {
      target.getSize(this);
    }
		
    // This is just the beginning of the callback, which shows the progress of the beginning
    if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
        && canNotifyStatusChanged()) {
      target.onLoadStarted(getPlaceholderDrawable());
    }
    if (IS_VERBOSE_LOGGABLE) {
      logV("finished run method in "+ LogTime.getElapsedMillis(startTime)); }}Copy the code

Let’s go straight to onSizeReady

  public synchronized void onSizeReady(int width, int height) { stateVerifier.throwIfRecycled(); .// These are some initialization states, configuration properties, we do not care.
 
  
    loadStatus =
  			/ / load
        engine.load(
            glideContext,
            model,
            requestOptions.getSignature(),
            this.width,
            this.height,
            requestOptions.getResourceClass(),
            transcodeClass,
            priority,
            requestOptions.getDiskCacheStrategy(),
            requestOptions.getTransformations(),
            requestOptions.isTransformationRequired(),
            requestOptions.isScaleOnlyOrNoTransform(),
            requestOptions.getOptions(),
            requestOptions.isMemoryCacheable(),
            requestOptions.getUseUnlimitedSourceGeneratorsPool(),
            requestOptions.getUseAnimationPool(),
            requestOptions.getOnlyRetrieveFromCache(),
            this,
            callbackExecutor);
  }
Copy the code

Keep tracking

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

    // Get the cache or request key
    EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);
		// Retrieve the resources in the active cache by keyEngineResource<? > active = loadFromActiveResources(key, isMemoryCacheable);// Call back the activity if it is in the cache
    if(active ! =null) {
      cb.onResourceReady(active, DataSource.MEMORY_CACHE);
      return null;
    }

    // Try to find this resource from LruResourceCacheEngineResource<? > cached = loadFromCache(key, isMemoryCacheable);if(cached ! =null) {
      // If there is a callback in the memory cache Lru
      cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
      return null;
    }

    / / -- -- -- -- -- -- -- -- -- -- -- -- -- go to the activities described here with memory cache cache didn't find -- -- -- -- -- -- -- -- -- -- -
    
    // See if the cache is executing based on the KeyEngineJob<? > current = jobs.get(key, onlyRetrieveFromCache);if(current ! =null) {
      // Call back the data if it is running
      current.addCallback(cb, callbackExecutor);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Added to existing load", startTime, key);
      }
      return new LoadStatus(cb, current);
    }

    // -------------- 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 current key to the cache
    jobs.put(key, engineJob);
		// Perform the task callback
    engineJob.addCallback(cb, callbackExecutor);
    // Start executing.
    engineJob.start(decodeJob);

    return new LoadStatus(cb, engineJob);
  }
Copy the code

Using the logic in engine.load, we can summarize a few points:

  1. Build the request or cache KEY first;
  2. Find the corresponding resource data (ActiveResources,LruResourceCache) from the memory cache based on the KEY, and call the corresponding onResourceReady to indicate that the data is ready.
  3. Find the corresponding key from the execution cache
    1. If yes, the command is being executed.
    2. If no, run enginejob. start to start a new request task.

EngineJob. Start = engineJob.

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

DecodeJob is a Runnable interface, where GlideExecutor thread pool starts execution, the run function will start DecodeJob, we track the implementation of run.

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

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

Tracking runWrapped

  private void runWrapped(a) {
    switch (runReason) {
      case INITIALIZE:
        // Get the resource status
        stage = getNextStage(Stage.INITIALIZE);
        // Obtain the resource actuator based on the current resource status
        currentGenerator = getNextGenerator();
        / / execution
        runGenerators();
        break; . }}private Stage getNextStage(Stage current) {
    switch (current) {
      case INITIALIZE:
        // If the resource caching policy is configured for the external call, return stage.resource_cache
       	// Otherwise proceed with stage.resource_cache execution.
        return diskCacheStrategy.decodeCachedResource()
            ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
      case RESOURCE_CACHE:
        // If the source data cache is configured externally, return stage.data_cache
        // Otherwise proceed with getNextStage(stage.data_cache)
        return diskCacheStrategy.decodeCachedData()
            ? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
      case DATA_CACHE:
        Return FINISHED if only data can be retrieved from the cache; otherwise, return SOURCE.
        // this means a new resource
        return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
      case SOURCE:
      case FINISHED:
        return Stage.FINISHED;
      default:
        throw new IllegalArgumentException("Unrecognized stage: "+ current); }}Copy the code

As you can see from the code above, we are looking for the executor of the resource. In this case, since we have no external caching policy, we load directly from the source data. See the code below.

  private DataFetcherGenerator getNextGenerator(a) {
    switch (stage) {
      // From the resource cache executor
      case RESOURCE_CACHE:
        return new ResourceCacheGenerator(decodeHelper, this);
        // Source data disk cache executor
      case DATA_CACHE:
        return new DataCacheGenerator(decodeHelper, this);
        // Nothing is configured, source data executor
      case SOURCE:
        return new SourceGenerator(decodeHelper, this);
      case FINISHED:
        return null;
      default:
        throw new IllegalStateException("Unrecognized stage: "+ stage); }}Copy the code

// Return the SourceGenerator source data actuator. Continue with the following code execution

  private void runGenerators(a) {
    currentThread = Thread.currentThread();
    startFetchTime = LogTime.getLogTime();
    boolean isStarted = false;
    // Determine whether to cancel, whether to start
    / / call DataFetcherGenerator. StartNext () to judge whether belong to start executing task
    while(! isCancelled && currentGenerator ! =null
        && !(isStarted = currentGenerator.startNext())) {
   
      ....
  }
Copy the code

. Here we see first currentGenerator startNext () this code, DataFetcherGenerator is an abstract class, then which perform the implementation of the class is here, you can refer to the following form;

Resource state describe Resource executor
Stage.RESOURCE_CACHE Retrieves cached resource data from disk. ResourceCacheGenerator
Stage.DATA_CACHE Retrieves cached source data from disk. DataCacheGenerator
Stage.SOURCE A new request task SourceGenerator

Since we are not configuring caching here, look directly at the SourceGenerator

  @Override
  public boolean startNext(a) {... loadData =null;
    boolean started = false;
    while(! started && hasNextModelLoader()) {// Get a ModelLoad loader
      loadData = helper.getLoadData().get(loadDataListIndex++);
      if(loadData ! =null
          && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
          || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
        started = true;
        // Use fetcher in the loader to load data according to priority
        loadData.fetcher.loadData(helper.getPriority(), this); }}return started;
  }
Copy the code

Helper.getloaddata () gets an HTTP request from helper.getloadData (). It is not cached, so we can guess the HTTP request.

List<LoadData<? >> getLoadData() {if(! isLoadDataSet) { isLoadDataSet =true;
      loadData.clear(); 
      // Get the loader from the Model registered by Glide (registration is done through Registry when Glide is initialized)
       //.append ()List<ModelLoader<Object, ? >> modelLoaders = glideContext.getRegistry().getModelLoaders(model);for (int i = 0, size = modelLoaders.size(); i < size; i++) { ModelLoader<Object, ? > modelLoader = modelLoaders.get(i); LoadData<? > current =// Start building the loader
            modelLoader.buildLoadData(model, width, height, options);
        // If the shelf is not empty, add temporary cache
        if(current ! =null) { loadData.add(current); }}}return loadData;
  }
Copy the code

Start by getting a container for the loader that was added via Registry.append() on Glide initialization, because we used the network link example. So, the implementation class for ModelLoad is the HttpGlideUrlLoader. Let’s look at the implementation

  @Override
  public LoadData<InputStream> buildLoadData(@NonNull GlideUrl model, int width, int height,
      @NonNull Options options) {
    GlideUrl url = model;
    if(modelCache ! =null) {
      url = modelCache.get(model, 0.0);
      if (url == null) {
        modelCache.put(model, 0.0, model); url = model; }}int timeout = options.get(TIMEOUT);
    return new LoadData<>(url, new HttpUrlFetcher(url, timeout));
  }
Copy the code

Here you see an HttpUrlFetcher returned to the loader. We have the loader, now start loading, return to the source code, look at the following:

class DataCacheGenerator implements DataFetcherGenerator.DataFetcher.DataCallback<Object> {
      
      // Select important code
       @Override
  public boolean startNext(a) {...while(! started && hasNextModelLoader()) { ModelLoader<File, ? > modelLoader = modelLoaders.get(modelLoaderIndex++); loadData = modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(), helper.getOptions());if(loadData ! =null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
        started = true;
        // Start loading data through the loader
        loadData.fetcher.loadData(helper.getPriority(), this); }}returnstarted; }}Copy the code

Because we just saw that the loader we got here is HttpUrlFetcher so let’s just look at the loadData implementation

  @Override
  public void loadData(@NonNull Priority priority,
      @NonNull DataCallback<? super InputStream> callback) {
    long startTime = LogTime.getLogTime();
    try {
      // HTTP request that returns an InputStream InputStream
      InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0.null, glideUrl.getHeaders());
      // Call InputStream back as a callback
      callback.onDataReady(result);
    } catch (IOException e) {
      callback.onLoadFailed(e);
    } finally{... }}Copy the code

How does the loadDataWithRedirects function produce 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)) {
      returngetStreamForSuccessfulRequest(urlConnection); }...// Let's ignore the exception for now
  }
Copy the code

HttpURLConnection is a web request made by HttpURLConnection as a Glide. A successful request returns an input stream that is finally called to the DecodeJob onDataFetcherReady via the onDataReady callback. Let’s follow the next callback

First, call back to SourceGenerator

  @Override
  public void onDataReady(Object data) {
    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
    if(data ! =null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
      dataToCache = data;
      cb.reschedule();
    } else{ cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher, loadData.fetcher.getDataSource(), originalKey); }}Copy the code

We’re going to have an else here because we didn’t configure the cache, so we’re going to call back.

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; // The key of the currently returned data
    this.currentData = data; // The data returned
    this.currentFetcher = fetcher; HttpUrlFetcher (HttpUrlFetcher)
    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 {
        // Parse the returned data
        decodeFromRetrievedData();
      } finally{ GlideTrace.endSection(); }}}... }// Parse the returned data
  private void decodeFromRetrievedData(a) {
    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);
    }
    // When parsing is complete, notify
    if(resource ! =null) {
      notifyEncodeAndRelease(resource, currentDataSource);
    } else{ runGenerators(); }}Copy the code

Continue to follow decodeFromData to see how to parse into Resource

  private <Data> Resource<R> decodeFromData(DataFetcher
        fetcher, Data data, DataSource dataSource) throws GlideException {... Resource<R> result = decodeFromFetcher(data, dataSource); .return result;
    } finally{ fetcher.cleanup(); }}@SuppressWarnings("unchecked")
  private <Data> Resource<R> decodeFromFetcher(Data data, DataSource dataSource)
      throws GlideException {
    // Get the parser LoadPath for the current data classLoadPath<Data, ? , R> path = decodeHelper.getLoadPath((Class<Data>) data.getClass());// Data is parsed through the LoadPath parser
    return runLoadPath(data, dataSource, path);
  }


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

To parse the data, we first build a LoadPath, then create a DataRewinder of type InputStreamRewinder, and finally put the data parsing operations into the loadPath. load method. Loadpath. load: loadpath. load: loadpath. load

  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);
    } finally{ listPool.release(throwables); }}private Resource<Transcode> loadWithExceptionList(DataRewinder<Data> rewinder,
      @NonNull Options options,
      int width, int height, DecodePath.DecodeCallback<ResourceType> decodeCallback,
      List<Throwable> exceptions) throws GlideException {
    Resource<Transcode> result = null;
   
    // Walk through the DecodePath collection stored internally to parse data through them
    for (int i = 0, size = decodePaths.size(); i < size; i++) {
      DecodePath<Data, ResourceType, Transcode> path = decodePaths.get(i);
      try {
        // This is where the data is actually parsed
        result = path.decode(rewinder, width, height, options, decodeCallback);
      } catch(GlideException e) { ... }...return result;
  }
Copy the code

With path. Decode

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

How does decodeResource resolve to an intermediate resource

  @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 to parse the dataresult = decoder.decode(data, width, height, options); }}catch (IOException | RuntimeException | OutOfMemoryError e) {
				...
      }

    return result;
  }
Copy the code

As you can see, the data parsing task is ultimately performed through DecodePath, which has three internal actions

  1. DeResource parses source data into resources

    • Source data: InputStream
    • Intermediate: Bitmap
  2. Call DecodeCallback. OnResourceDecoded processing resources

  3. Call ResourceTranscoder. Transcode resources into the target resource

    • Target resource type: Drawable

Decode through the above decoder. Decode source code, it is an interface, because we here the source data is InputStream, so, its implementation class is StreamBitmapDecoder, we will look at its internal decoding process

 @Override
  public Resource<Bitmap> decode(@NonNull InputStream source, int width, int height,
      @NonNull Options options)
      throws IOException {

    // Use to fix the mark limit to avoid allocating buffers that fit entire images.
    final RecyclableBufferedInputStream bufferedStream;
    final booleanownsBufferedStream; .try {
      
       
        
        
       
      
      return downsampler.decode(invalidatingStream, width, height, options, callbacks);
    } finally{... }}Copy the code

Now we have a Bitmap data, and we need to call it back. Please look at the following code

  public Resource<Transcode> decode(DataRewinder<DataType> rewinder, int width, int height,
      @NonNull Options options, DecodeCallback<ResourceType> callback) throws GlideException {
    //1. Call decodeResourec to parse data into an intermediate resource Bitmap
    Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options);
    //2. Call back the data after parsing
    Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);
    //3. Convert the resource to the target resource Bitmap to Drawable
    return transcoder.transcode(transformed, options);
  }
Copy the code

Call back to DecodeJob (DecodeJob

class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback.Runnable.Comparable<DecodeJob<? > >,Poolable {...@Override
    public Resource<Z> onResourceDecoded(@NonNull Resource<Z> decoded) {
      return DecodeJob.this.onResourceDecoded(dataSource, decoded); }... }Copy the code

To continue with

  @Synthetic
  @NonNull
  <Z> Resource<Z> onResourceDecoded(DataSource dataSource, @NonNull Resource
       
         decoded)
        {
    @SuppressWarnings("unchecked")
    // Get the resource type
    Class<Z> resourceSubClass = (Class<Z>) decoded.get().getClass();
    Transformation<Z> appliedTransformation = null;
    Resource<Z> transformed = decoded;
    // If it is not obtained from disk resources, perform the transform operation
    if (dataSource != DataSource.RESOURCE_DISK_CACHE) {
      appliedTransformation = decodeHelper.getTransformation(resourceSubClass);
      transformed = appliedTransformation.transform(glideContext, decoded, width, height);
    }
    
    ...
		// Build the data encoding strategy
    final EncodeStrategy encodeStrategy;
    final ResourceEncoder<Z> encoder;
    if (decodeHelper.isResourceEncoderAvailable(transformed)) {
      encoder = decodeHelper.getResultEncoder(transformed);
      encodeStrategy = encoder.getEncodeStrategy(options);
    } else {
      encoder = null;
      encodeStrategy = EncodeStrategy.NONE;
    }
		
    // Build the cache Key according to the encoding policy
    Resource<Z> result = transformed;
    booleanisFromAlternateCacheKey = ! decodeHelper.isSourceKey(currentSourceKey);if (diskCacheStrategy.isResourceCacheable(isFromAlternateCacheKey, dataSource,
        encodeStrategy)) {
      if (encoder == null) {
        throw new Registry.NoResultEncoderAvailableException(transformed.get().getClass());
      }
      final Key key;
      switch (encodeStrategy) {
        case SOURCE:
          // Source data key
          key = new DataCacheKey(currentSourceKey, signature);
          break; . }// Initialize the code manager to commit the memory cache
      LockedResource<Z> lockedResult = LockedResource.obtain(transformed);
      deferredEncodeManager.init(key, encoder, lockedResult);
      result = lockedResult;
    }
    // Return the transformed Bitmap
    return result;
  }
Copy the code

As you can see, onResourceDecoded mainly does the following operations for intermediate resources

  1. A resource conversion operation was performed. Examples include Fit_Center and CenterCrop, which are configured at request time.
  2. The key to build the disk cache.

The last step is to convert a Bitmap to a Drawable, as shown in the following code

public class DecodePath<DataType.ResourceType.Transcode> {...Resource<Transcode> decode(DataRewinder<DataType> rewinder, int width, int height,
      @NonNull Options options, DecodeCallback<ResourceType> callback) throws GlideException {
    //1. Call decodeResourec to parse data into an intermediate resource Bitmap
    Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options);
    //2. Call back the data after parsing
    Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);
    //3. Convert the resource to the target resource Bitmap to Drawable
    returntranscoder.transcode(transformed, options); }... }Copy the code

The ResourceTranscoder is an interface, and since the resolved data is a Bitmap, its implementation class is BitmapDrawableTranscoder. Finally, look at the specific implementation of its Transcode

public class BitmapDrawableTranscoder implements ResourceTranscoder<Bitmap.BitmapDrawable> {

   @Nullable
  @Override
  public Resource<BitmapDrawable> transcode(@NonNull Resource
       
         toTranscode, @NonNull Options options)
        {
    returnLazyBitmapDrawableResource.obtain(resources, toTranscode); }}Copy the code

Specific we see LazyBitmapDrawableResource. Obtain

public final class LazyBitmapDrawableResource implements Resource<BitmapDrawable>,
    Initializable {

  private final Resources resources;
  private final Resource<Bitmap> bitmapResource;


  @Deprecated
  public static LazyBitmapDrawableResource obtain(Context context, Bitmap bitmap) {
    return
        (LazyBitmapDrawableResource)
            obtain(
                context.getResources(),
                BitmapResource.obtain(bitmap, Glide.get(context).getBitmapPool()));
  }

  @Deprecated
  public static LazyBitmapDrawableResource obtain(Resources resources, BitmapPool bitmapPool, Bitmap bitmap) {
    return
        (LazyBitmapDrawableResource) obtain(resources, BitmapResource.obtain(bitmap, bitmapPool));
  }

  @Nullable
  public static Resource<BitmapDrawable> obtain( @NonNull Resources resources, @Nullable Resource
       
         bitmapResource)
        {
    if (bitmapResource == null) {
      return null;
    }
    return new LazyBitmapDrawableResource(resources, bitmapResource);

  }

  private LazyBitmapDrawableResource(@NonNull Resources resources, @NonNull Resource
       
         bitmapResource)
        {
    this.resources = Preconditions.checkNotNull(resources);
    this.bitmapResource = Preconditions.checkNotNull(bitmapResource);
  }

  @NonNull
  @Override
  public Class<BitmapDrawable> getResourceClass(a) {
    return BitmapDrawable.class;
  }

   // Get returns a BitmapDrawable object
  @NonNull
  @Override
  public BitmapDrawable get(a) {
    return new BitmapDrawable(resources, bitmapResource.get());
  }

  @Override
  public int getSize(a) {
    return bitmapResource.getSize();
  }

  @Override
  public void recycle(a) {
    bitmapResource.recycle();
  }

  @Override
  public void initialize(a) {
    if (bitmapResource instanceofInitializable) { ((Initializable) bitmapResource).initialize(); }}}Copy the code

Also completed the transformation, we resolve to store bitmap to LazyBitmapDrawableResource inside, and then the outside world through the get method can get a BitmapDrawable object.

After parsing, it’s time to display the data. Look at the following code:

class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback.Runnable.Comparable<DecodeJob<? > >,Poolable {

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

Note 1 has parsed the data, now execute notifyEncodeAndRelease

private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {...// Notify the calling layer that the data is ready
    notifyComplete(result, dataSource);

    stage = Stage.ENCODE;
    try {
      // Cache the resource disk
      if(deferredEncodeManager.hasResourceToEncode()) { deferredEncodeManager.encode(diskCacheProvider, options); }}finally{... }/ / finish
    onEncodeComplete();
  }

  private void notifyComplete(Resource<R> resource, DataSource dataSource) {
    setNotifiedOrThrow();
    // In the DecodeJob build, we know that the Callback is a EngineJob
    callback.onResourceReady(resource, dataSource);
  }
Copy the code

Can see DecodeJob. Mainly in decodeFromRetrievedData made several operations

  1. Parse the returned resources.
  2. Get parsed resources and, if local caching is configured, cache them to disk.
  3. Notify the upper layer that the resource is ready for use.

Let’s look directly at the EngineJob’s onResourceReady callback

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


  @Synthetic
  void notifyCallbacksOfResult(a) { 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 to the upper Engine is complete
    listener.onEngineJobComplete(this, localKey, localResource);

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

The onResourceReady callback for EngineJob does two things

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

We look at the listener. OnEngineJobComplete concrete realization

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

    jobs.removeIfCurrent(key, engineJob);
  }
Copy the code

Finally, call ImageViewTarget. Let’s see what happens:

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

    private final ResourceCallback cb;

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

    @Override
    public void run(a) {
      synchronized (EngineJob.this) {
        if (cbs.contains(cb)) {
         ...
          // Returns the prepared resourcecallCallbackOnResourceReady(cb); removeCallback(cb); } decrementPendingCallbacks(); }}}Copy the code

Through code can see CallResourceReady implement Runnable them, when entry. The executor, execute the thread pool implementation is invoked when the run, finally, we continue with callCallbackOnResourceReady

  @Synthetic
  synchronized void callCallbackOnResourceReady(ResourceCallback cb) {
    try {
      // callback to SingleRequest
      cb.onResourceReady(engineResource, dataSource);
    } catch (Throwable t) {
      throw newCallbackException(t); }}Copy the code

Follow the SingleRequest onResourceReady callback implementation

 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 the resource is 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);
        // Callback to target ImageViewTarget resource readytarget.onResourceReady(result, animation); }}finally {
      isCallingCallbacks = false;
    }

  	// The load succeeded
    notifyLoadSuccess();
  }

Copy the code

This step calls back the prepared resource to the display layer, as shown in the following code

public abstract class ImageViewTarget<Z> extends ViewTarget<ImageView.Z>
    implements Transition.ViewAdapter {...@Override
  public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
    if (transition == null| |! transition.transition(resource,this)) {
      setResourceInternal(resource);
    } else{ maybeUpdateAnimatable(resource); }}protected abstract void setResource(@Nullable Z resource); . }private void setResourceInternal(@Nullable Z resource) {
    // Call the setResource function to display the resourcesetResource(resource); . }Copy the code

At the beginning of the build, we know that the implementation class is BitmapImageViewTarget only when we call asBitmap. In this case, the test does not call this function, so its implementation class is DrawableImageViewTarget.

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

Here we see that the abstract class calls setResource, and the subclass implements and calls View.setimagedRawable (Resource); The picture is actually showing up now. Glide. With (activity). Load (http://xxx.png).into(imageView) this process is finished. Here’s a summary of the process.

conclusion

By this Glide. With (activity).load(http://xxx.png).into(imageView) a short code, the whole Glide picture load display process we have been familiar with, and look at the source code can not be a line by line to understand its internal specific implementation, We just have to pick a point, say, with and then we just focus on the implementation of with, and we don’t care about anything else. The following is a simple talk about the whole process of Glide:

In the process of reading, I hope you can point out any questions or mistakes in the description of the article.

Thank you for reading, thank you!

reference

  • The official Glide
  • Guo Lin Daishen Glide source code analysis
  • Glide 4.9 source code analysis
  • Glide source code analysis