Glide Picture loading framework in the last article, we introduced the use of Glide picture loading framework, through previous learning, we may be able to proficiently use the Glide picture loading framework into our project, but if someone asks you how it is loaded, how does it work? Why does a custom GlideModule just need to add meta-data to the Manifest file? And so on a lot of loading processes and use considerations. Of course, in order to understand these problems, we need to Glide source code to have a general understanding, to analyze the mystery of the deep source. Let’s go into the world of Glide source code, this article analysis is Glide 3.7.0 version. Special reminder, before reading this article to the use of Glide to have a first understanding, you can first read the last article, detail picture loading framework Glide – application.

This article is a record of my own learning, and I am very glad if it is helpful to you. Of course, if there are deficiencies or mistakes in the article, please correct them so as to avoid me giving wrong guidance to other readers.

If you’ve read the last article, or if you’ve used Glide, you know that the easiest way to load an image is to Glide

Glide. With (context). The load (url). The placeholder (R.d rawable. The placeholder). Into (imageView).Copy the code

So this article with this simple code as the main line, step by step into the source Glide.

Glide.with(context)

   // Get the RequestManager object, which implements the LifeCycleListener interface and binds the Activity/Fragment lifecycle to suspend, resume, and clear requests
    public static RequestManager with(Context context) {
    / / get RequestManagerRetriever instance, the class will note RequestManager and custom fragments (such as RequestManagerFragment SupportRequestManagerFragment) binding, Manage callbacks in the life cycle
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(context);
    }Copy the code

Glide has four static overloaded methods with(), each of which internally obtains a RequestManager object from the corresponding RequestManagerRetriever retriever get overloaded method. The benefit of providing various overloaded methods is that you can tie Glide’s load request to the Activity/Fragment lifecycle to automatically execute the request and suspend the operation.

Next, we use the Activity parameter to analyze how Glide requests and bind life cycles automatically request, suspend, and destroy.


    public RequestManager get(Activity activity) {
        if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
            return get(activity.getApplicationContext());
        } else {

           // Determine whether the activity is already destroyed
            assertNotDestroyed(activity);
            // Get the FragmentManager object
            android.app.FragmentManager fm = activity.getFragmentManager();
            // Create a Fragment, RequestManager and bind it
            returnfragmentGet(activity, fm); }}Copy the code

AssertNotDestroyed mainly asserts whether the Activity has been Destroyed. If there is no destruction, or the Activity’s FragmentManager, then return the RequestManager via fragmentGet.

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    RequestManager fragmentGet(Context context, android.app.FragmentManager fm) {
      //* Obtain a RequestManagerFragment, using Frament to manage the request lifecycle
        RequestManagerFragment current = getRequestManagerFragment(fm);
        RequestManager requestManager = current.getRequestManager();
        // If the requestManager is empty, the initial requestManager is loaded and setRequestManager is called to set the fragment to the RequestManagerFragment
        if (requestManager == null) {
            requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
            current.setRequestManager(requestManager);
        }
        return requestManager;
    }

 // Get the Fragment object
    RequestManagerFragment getRequestManagerFragment(final android.app.FragmentManager fm) {
        RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
        if (current == null) {
            current = pendingRequestManagerFragments.get(fm);
            if (current == null) {
                current = newRequestManagerFragment(); pendingRequestManagerFragments.put(fm, current); fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss(); handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget(); }}return current;
    }Copy the code

Finally through getRequestManagerFragment () method to obtain a RequestManagerFragment object.

public class RequestManagerFragment extends Fragment {
    private final ActivityFragmentLifecycle lifecycle;
    // omit some code...
    @Override
    public void onStart(a) {
        super.onStart();
        Lifecycle is associated with the corresponding onStart method
        lifecycle.onStart();
    }

    @Override
    public void onStop(a) {
        super.onStop();
         Lifecycle is associated with the corresponding onStop method
        lifecycle.onStop();
    }

    @Override
    public void onDestroy(a) {
        super.onDestroy();
         Lifecycle is associated with the corresponding onDestroy methodlifecycle.onDestroy(); }}Copy the code

At this point we see that the RequestManagerFragment inherits the Fragment. And in its life cycle onStart (), onStop (), onDestory (), call the ActivityFragmentLifecycle corresponding method, ActivityFragmentLifecycle implements the Lifecycle interface, LifecycleListener Listener) callback corresponding cycle methods (LifecycleListener onStart(),onStop(),onDestory()). LifecycleListener is the interface that listens on the lifecycle time. Back to the next line of code in the fragmentGet method


/ / create RequestManager incoming Lifecycle implementation class, such as ActivityFragmentLifecycle
requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());Copy the code

For the RequestManager class, which implements LifecycleListener, the following code

/** * A class for managing and starting requests for Glide. Can use activity, fragment and connectivity lifecycle events to * intelligently stop, start, and restart requests. Retrieve either by instantiating a new object, or to take advantage * built in Activity and Fragment lifecycle handling, use the static Glide.load methods with your Fragment or Activity. */
public class RequestManager implements LifecycleListener {
  //An interface for listening to Activity/Fragment lifecycle events.
    private final Lifecycle lifecycle;
 public RequestManager(Context context, Lifecycle lifecycle, RequestManagerTreeNode treeNode) {
        this(context, lifecycle, treeNode, new RequestTracker(), new ConnectivityMonitorFactory());
    }

    RequestManager(Context context, final Lifecycle lifecycle, RequestManagerTreeNode treeNode,
            RequestTracker requestTracker, ConnectivityMonitorFactory factory) {
        this.context = context.getApplicationContext();
        this.lifecycle = lifecycle;
        this.treeNode = treeNode;
        //A class for tracking, canceling, and restarting in progress, completed, and failed requests.
        this.requestTracker = requestTracker;
        // Obtain a Glide instance through the static method of Glide. The singleton pattern
        this.glide = Glide.get(context);
        this.optionsApplier = new OptionsApplier();

/ / by the factory class ConnectivityMonitorFactory obtained the build ConnectivityMonitor (an interface used to monitor the network connection events)
        ConnectivityMonitor connectivityMonitor = factory.build(context,
                new RequestManagerConnectivityListener(requestTracker));

        // If we're the application level request manager, we may be created on a background thread. In that case we
        // cannot risk synchronously pausing or resuming requests, so we hack around the issue by delaying adding
        // ourselves as a lifecycle listener by posting to the main thread. This should be entirely safe.
        if (Util.isOnBackgroundThread()) {
            new Handler(Looper.getMainLooper()).post(new Runnable() {
                @Override
                public void run(a) {
                    lifecycle.addListener(RequestManager.this); }}); }else {
        // Set the listener
            lifecycle.addListener(this);
        }
        lifecycle.addListener(connectivityMonitor);
    }
    /** * Lifecycle callback that registers for connectivity events (if the android.permission.ACCESS_NETWORK_STATE * permission is present) and restarts failed or paused requests. */
    @Override
    public void onStart(a) {
        // onStart might not be called because this object may be created after the fragment/activity's onStart method.
        resumeRequests();
    }

    /** * Lifecycle callback that unregisters for connectivity events (if the android.permission.ACCESS_NETWORK_STATE * permission is present) and pauses in progress loads. */
    @Override
    public void onStop(a) {
        pauseRequests();
    }

    /** * Lifecycle callback that cancels all in progress requests and clears and recycles resources for all completed * requests. */
    @Override
    public void onDestroy(a) { requestTracker.clearRequests(); }}Copy the code

It will pass lifeCycle of the fragment that has just been created and add the RequestManager listener to lifeCycle to implement the binding. You see requestTracker in the constructor of the RequestManager, which tracks requests canceled, restarted, completed, and failed. The RequestManagerFragment is used to connect to lifecycle methods, the RequestManager is used to implement the lifecycle request methods, and the RequestManagerRetriever is bound to the RequestManager.

GlideModule implementation

Glide. Get (context) is also initialized in the constructor; The Glide object is initialized

    /**
     * Get the singleton.
     *
     * @return the singleton
     */
    public static Glide get(Context context) {

        if (glide == null) {
        / / synchronize Glide
            synchronized (Glide.class) {
                if (glide == null) {
                    Context applicationContext = context.getApplicationContext();
                    // Parse the metadata tag of the custom GlideModule configured in the manifest file to return a GlideModule collection
                    List<GlideModule> modules = new ManifestParser(applicationContext).parse();

                    GlideBuilder builder = new GlideBuilder(applicationContext);
                    // A collection of loops that execute methods in the GlideModule implementation class
                    for (GlideModule module : modules) {
                        module.applyOptions(applicationContext, builder);
                    }
                    glide = builder.createGlide();
                    for (GlideModule module : modules) {
                    // Register the componentmodule.registerComponents(applicationContext, glide); }}}}return glide;
    }Copy the code

The instance is obtained singletons using the GET method, and the GlideModule configuration is implemented during initialization. How do you do that? On initialization, a ManifestParser object is new and the parse () method is called to return a List of type GlideModule.

Parse the metadata implementation
    public List<GlideModule> parse(a) {
        List<GlideModule> modules = new ArrayList<GlideModule>();
        try {
        // The PackageManager obtains all metadata information
            ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(
                    context.getPackageName(), PackageManager.GET_META_DATA);
             // Manifest file contains metadata
            if(appInfo.metaData ! =null) {
            // Metadata is traversed by key (for GlideModule, key is the full path class name of GlideModule's implementation class)
                for (String key : appInfo.metaData.keySet()) {
                // Filter key value equal to GLIDE_MODULE_VALUE (string GlideModule)
                    if (GLIDE_MODULE_VALUE.equals(appInfo.metaData.get(key))) {
                        // Add to setmodules.add(parseModule(key)); }}}}catch (PackageManager.NameNotFoundException e) {
            throw new RuntimeException("Unable to find metadata to parse GlideModules", e);
        }

        return modules;
    }Copy the code

The parse () method uses the getApplicationInfo method to obtain metaData information. If metaData data (appInfo.metadata! If the metaData is GlideModule, parseModule (key) is called. The method returns GlideModule and adds GlideModule to the returned List.

Look at the parseModule(String className) method

// Get the GlideModule instance through reflection
    private static GlideModule parseModule(String className) { Class<? > clazz;try {
            clazz = Class.forName(className);
        } catch (ClassNotFoundException e) {
            throw new IllegalArgumentException("Unable to find GlideModule implementation", e);
        }

        Object module;
        try {
            module = clazz.newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException("Unable to instantiate GlideModule implementation for " + clazz, e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Unable to instantiate GlideModule implementation for " + clazz, e);
        }

        if(! (moduleinstanceof GlideModule)) {
            throw new RuntimeException("Expected instanceof GlideModule, but found: " + module);
        }
        return (GlideModule) module;
    }Copy the code

Here we see the custom GlideModule object we declared in the manifest being retrieved by reflection. After getting the GlideModule collection, the collection is iterated over and the corresponding applyOptions and registerComponents methods are called. The Glide object generation is created through the createGlide method of GlideBuilder.

 Glide createGlide(a) {
        if (sourceService == null) {
            final int cores = Math.max(1, Runtime.getRuntime().availableProcessors());
            // Initialize the thread pool
            sourceService = new FifoPriorityThreadPoolExecutor(cores);
        }
        if (diskCacheService == null) {
            diskCacheService = new FifoPriorityThreadPoolExecutor(1);
        }

        MemorySizeCalculator calculator = new MemorySizeCalculator(context);
         // Set the Bitmap pool
        if (bitmapPool == null) {

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
                int size = calculator.getBitmapPoolSize();
                bitmapPool = new LruBitmapPool(size);
            } else {
                bitmapPool = newBitmapPoolAdapter(); }}if (memoryCache == null) {
            memoryCache = new LruResourceCache(calculator.getMemoryCacheSize());
        }

        if (diskCacheFactory == null) {
        // Internal disk cache
            diskCacheFactory = new InternalCacheDiskCacheFactory(context);
        }

        if (engine == null) {
        // Initialize the engine class
            engine = new Engine(memoryCache, diskCacheFactory, diskCacheService, sourceService);
        }

        if (decodeFormat == null) {
            decodeFormat = DecodeFormat.DEFAULT;
        }

        return new Glide(engine, memoryCache, bitmapPool, context, decodeFormat);
    }Copy the code

See this is all doing some initialization and passing the parameters to the Glide constructor. Glide construction method to do are some default initialization operations, you can go to view the source code, no longer posted here.

The metaData key is the full path name of the GlideModule. The metaData key is the default path name of the GlideModule. Value The value must be GlideModule. You’ll see that when we don’t want our custom GlideModule to work, we just need to remove the corresponding GlideModule. Why configure when obfuscation is used…

-keep public class * implements com.bumptech.glide.module.GlideModuleCopy the code

requestManager.load

The load method can also accept overloaded methods of String, Url, Integer, etc. In this case, we analyze the String parameter.

    public DrawableTypeRequest<String> load(String string) {
        return (DrawableTypeRequest<String>) fromString().load(string);
    }
    public DrawableTypeRequest<String> fromString(a) {
        return loadGeneric(String.class);
    }
   private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass) {
        // Omit a piece of code

        return optionsApplier.apply(
        // Create DrawableTypeRequest, a subclass of GenericRequestBuilder
                new DrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context,
                        glide, requestTracker, lifecycle, optionsApplier));
    }Copy the code
    @Override
    public DrawableRequestBuilder<ModelType> load(ModelType model) {
    // Call the freloadd method
        super.load(model);
        return this;
    }Copy the code

The DrawableTypeRequest object is returned, and the DrawableTypeRequest inheritance is as followsFor the equestBuilder class, there is a creator mode. For the popular placeholder functions placeholder (), error (), transform, etc.

    / * * * {@inheritDoc} * /
    @Override
    public DrawableRequestBuilder<ModelType> placeholder(Drawable drawable) {
        super.placeholder(drawable);
        return this;
    }Copy the code

We see that the parent method is eventually called again

    /**
     * Sets an Android resource id for a {@link android.graphics.drawable.Drawable} resourceto display while a resource
     * is loading.
     *
     * @param resourceId The id of the resource to use as a placeholder
     * @return This request builder.
     */
    public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> placeholder(
            int resourceId) {
        this.placeholderId = resourceId;

        return this;
    }Copy the code

If you look at the GenericRequestBuilder source, you’ll see that every time we call placeholder (), error(), etc., we’re actually assigning values to variables in that class.

After a series of operations, the into(imageView) method is called to complete the final loading of the image

Create a request

    /**
     * Sets the {@link ImageView} the resource will be loaded into, cancels any existing loads into the view, and frees
     * any resources Glide may have previously loaded into the view so they may be reused.
     *
     * @see Glide#clear(android.view.View)
     *
     * @param view The view to cancel previous loads for and load the new resource into.
     * @return The {@link com.bumptech.glide.request.target.Target} used to wrap the given {@link ImageView}.
     */
    public Target<TranscodeType> into(ImageView view) {
        Util.assertMainThread();
        if (view == null) {
            throw new IllegalArgumentException("You must pass in a non null View");
        }

        if(! isTransformationSet && view.getScaleType() ! =null) {
            switch (view.getScaleType()) {
                case CENTER_CROP:
                    applyCenterCrop();
                    break;
                case FIT_CENTER:
                case FIT_START:
                case FIT_END:
                    applyFitCenter();
                    break;
                //$CASES-OMITTED$
                default:
                    // Do nothing.}}return into(glide.buildImageViewTarget(view, transcodeClass));
    }Copy the code

As you can see above, the final call to the into method is

    /**
     * Set the target the resource will be loaded into.
     *
     * @see Glide#clear(com.bumptech.glide.request.target.Target)
     *
     * @param target The target to load the resource into.
     * @return The given target.
     */
    public <Y extends Target<TranscodeType>> Y into(Y target) {
        Util.assertMainThread();
        if (target == null) {
            throw new IllegalArgumentException("You must pass in a non null Target");
        }
        if(! isModelSet) {throw new IllegalArgumentException("You must first set a model (try #load())");
        }

// Get the Request object
        Request previous = target.getRequest();
//requestTracker is a request tracking object that manages requests initiated, paused, and cleared
        if(previous ! =null) {
            previous.clear();
            requestTracker.removeRequest(previous);
            previous.recycle();
        }

       // Create the request object
        Request request = buildRequest(target);
        target.setRequest(request);
        // Add target to lifecycle
        lifecycle.addListener(target);
        // Execute the request
        requestTracker.runRequest(request);

        return target;
    }Copy the code

All of the above calls util.assertMainThread (); Judgments can only be performed in the main thread. (Update View of course needs to be in the main thread), in Glide we can understand Target as View, but Glide is a layer of encapsulation of our View. The request object is then created via buildRequest.

// Create the request object
   private Request buildRequest(Target<TranscodeType> target) {
        if (priority == null) {
        // The default loading priority is NORMAL
            priority = Priority.NORMAL;
        }
        / / create Request
        return buildRequestRecursive(target, null);
    }

    private Request buildRequestRecursive(Target<TranscodeType> target, ThumbnailRequestCoordinator parentCoordinator) {
        if(thumbnailRequestBuilder ! =null) {
            if (isThumbnailBuilt) {
                throw new IllegalStateException("You cannot use a request as both the main request and a thumbnail, "
                        + "consider using clone() on the request(s) passed to thumbnail()");
            }
            // Recursive case: contains a potentially recursive thumbnail request builder.
            if(thumbnailRequestBuilder.animationFactory.equals(NoAnimation.getFactory())) { thumbnailRequestBuilder.animationFactory =  animationFactory; }if (thumbnailRequestBuilder.priority == null) {
                thumbnailRequestBuilder.priority = getThumbnailPriority();
            }

            if(Util.isValidDimensions(overrideWidth, overrideHeight) && ! Util.isValidDimensions(thumbnailRequestBuilder.overrideWidth, thumbnailRequestBuilder.overrideHeight)) { thumbnailRequestBuilder.override(overrideWidth, overrideHeight); } ThumbnailRequestCoordinator coordinator =new ThumbnailRequestCoordinator(parentCoordinator);
            Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator);
            // Guard against infinite recursion.
            isThumbnailBuilt = true;
            // Recursively generate thumbnail requests.
            Request thumbRequest = thumbnailRequestBuilder.buildRequestRecursive(target, coordinator);
            isThumbnailBuilt = false;
            coordinator.setRequests(fullRequest, thumbRequest);
            return coordinator;
        } else if(thumbSizeMultiplier ! =null) {
            // Base case: thumbnail multiplier generates a thumbnail request, but cannot recurse.
            ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);
            Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator);
            Request thumbnailRequest = obtainRequest(target, thumbSizeMultiplier, getThumbnailPriority(), coordinator);
            coordinator.setRequests(fullRequest, thumbnailRequest);
            return coordinator;
        } else {
            // Base case: no thumbnail.
            returnobtainRequest(target, sizeMultiplier, priority, parentCoordinator); }}Copy the code

Finally, the obtainRequest method is called

    private Request obtainRequest(Target<TranscodeType> target, float sizeMultiplier, Priority priority,
            RequestCoordinator requestCoordinator) {
        return GenericRequest.obtain(
                loadProvider,
                model,
                signature,
                context,
                priority,
                target,
                sizeMultiplier,
                placeholderDrawable,
                placeholderId,
                errorPlaceholder,
                errorId,
                fallbackDrawable,
                fallbackResource,
                requestListener,
                requestCoordinator,
                glide.getEngine(),
                transformation,
                transcodeClass,
                isCacheable,
                animationFactory,
                overrideWidth,
                overrideHeight,
                diskCacheStrategy);
    }Copy the code

It was eventually created using the GenericRequest.obtain method

    public static <A, T, Z, R> GenericRequest<A, T, Z, R> obtain(...). {
        @SuppressWarnings("unchecked")
        GenericRequest<A, T, Z, R> request = (GenericRequest<A, T, Z, R>) REQUEST_POOL.poll();
        if (request == null) {
            request = new GenericRequest<A, T, Z, R>();
        }
        // Initializes the Request object with the set parametersrequest.init(...) ;// Return the Request object
        return request;
    }Copy the code

After the buildRequest request is successfully created, target.setrequest (request) is used. Set the request to target and add target to Lifecycle via addListener. Performed so much above all just request creation, request is executed through requestTracker. RunRequest (request); In the first place.

Send the request

    /** * Starts tracking the given request. */
    public void runRequest(Request request) {
    // Add the request object to the collection
        requests.add(request);
        if(! isPaused) {// If the current state is not paused, call begin to send the request
            request.begin();
        } else {
        // Add the request to the pending request collectionpendingRequests.add(request); }}Copy the code

In the last few lines of code, we saw that each time a request is submitted, the request is added to a set, used to manage the request, and then viewed through the Request implementation class GenericRequest to see what the BEGIN method does

    / * * * {@inheritDoc} * /
    @Override
    public void begin(a) {
        startTime = LogTime.getLogTime();
        if (model == null) {
        // Load incorrect placeholder Settings
            onException(null);
            return;
        }

        status = Status.WAITING_FOR_SIZE;
        // Verify that the width and height are valid
        if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
            // Send the request
            onSizeReady(overrideWidth, overrideHeight);
        } else {
            target.getSize(this);
        }

        if(! isComplete() && ! isFailed() && canNotifyStatusChanged()) {// Default placeholder setting callback before loading
            target.onLoadStarted(getPlaceholderDrawable());
        }
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logV("finished run method in "+ LogTime.getElapsedMillis(startTime)); }}// Get the Drawable object that sets the placeholder image at the start of the load
    private Drawable getPlaceholderDrawable(a) {
        if (placeholderDrawable == null && placeholderResourceId > 0) {
            placeholderDrawable = context.getResources().getDrawable(placeholderResourceId);
        }
        return placeholderDrawable;
    }Copy the code

It said! isComplete() && ! OnLoadStarted (getNotifyStatusChanged ()) isFailed() &&canNotifyStatusChanged (); We can see the onLoadStarted callback execution statement in Target’s implementation class ImageViewTarget

// Set the ImageView Drawable
    @Override
    public void onLoadStarted(Drawable placeholder) {
        view.setImageDrawable(placeholder);
    }Copy the code

Now you have a feeling of a bright future, finally understand why after setting the placeHolder, there will be a placeHolder before loading, of course, the principle of setting the loading error image placeHolder is the same. The timing of the callback is different.

    /** * A callback method that should never be invoked directly. */
    @Override
    public void onSizeReady(int width, int height) {
// Omit some code
        status = Status.RUNNING;// Update the request status to the running state
// Omit some code

// An entry to the Engine that requests the core method to execute
        loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
                priority, isMemoryCacheable, diskCacheStrategy, this); loadedFromMemoryCache = resource ! =null;
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logV("finished onSizeReady in "+ LogTime.getElapsedMillis(startTime)); }}Copy the code

The Engine class encapsulates important entry methods for retrieving data and provides these apis to the Request layer, such as load(), Release (), clearDiskCache(), and so on

    public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
            DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
            Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
            // Assert whether the thread is in the main thread
        Util.assertMainThread();
        long startTime = LogTime.getLogTime();

        final String id = fetcher.getId();
        / / create Enginekey
        EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
                loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
                transcoder, loadProvider.getSourceEncoder());
// Load images from cacheEngineResource<? > cached = loadFromCache(key, isMemoryCacheable);if(cached ! =null) {
        // onResourceReady() is called back to target
            cb.onResourceReady(cached);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Loaded resource from cache", startTime, key);
            }
            return null;
        }
// Try to fetch from active Resources, which represents the Resources currently in use, unlike the in-memory cache, which is not cleared when the cache is clear.EngineResource<? > active = loadFromActiveResources(key, isMemoryCacheable);if(active ! =null) {
           // Get a successful callback
            cb.onResourceReady(active);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Loaded resource from active resources", startTime, key);
            }
            return null;
        }

        EngineJob current = jobs.get(key);
        // Determine if a task already exists in Jobs. If so, the task has already been submitted
        if(current ! =null) {
            current.addCallback(cb);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Added to existing load", startTime, key);
            }
            return new LoadStatus(cb, current);
        }

// if the cache fails to get the EngineJob, create an EngineJob object
        EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
        DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
                transcoder, diskCacheProvider, diskCacheStrategy, priority);
         //EngineRunnable is the entry to the task execution phase
        EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
        jobs.put(key, engineJob);
        engineJob.addCallback(cb);
        // Start submitting the job
        engineJob.start(runnable);

        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Started new load", startTime, key);
        }
        return new LoadStatus(cb, engineJob);
    }Copy the code

If the return value is empty, it will be loaded again from the active resource. If it is empty again, it will check whether Jobs submitted the task. If not, EngineRunnable will be created and the task will be submitted to the engineJob. Let’s look at the start method in EngineJob

   // Submit the task and add it to the thread pool
    public void start(EngineRunnable engineRunnable) {
        this.engineRunnable = engineRunnable;
        // Submit the task to the diskCacheService thread pool
        future = diskCacheService.submit(engineRunnable);
    }Copy the code

Next, look at the run method of the thread class EngineRunnable, which is the entry point for task execution

// Task run entry
 @Override
    public void run(a) {
        if (isCancelled) {
            return;
        }

        Exception exception = null; Resource<? > resource =null;
        try {
        // Data acquisition, codec
            resource = decode();
        } catch (Exception e) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "Exception decoding", e);
            }
            exception = e;
        }
// If the current state is cancelled, all resources are reclaimed to prevent memory leaks
        if (isCancelled) {
            if(resource ! =null) {
                resource.recycle();
            }
            return;
        }

        if (resource == null) {
        // Load failed callback
            onLoadFailed(exception);
        } else {
        // Successfully loaded callbackonLoadComplete(resource); }}privateResource<? > decode()throws Exception {
        if (isDecodingFromCache()) {
        //// retrieves data from DiskLruCache and decodes it
            return decodeFromCache();
        } else {
        // Retrieve and decode data from other sources, such as network, local File, data stream, etc
            returndecodeFromSource(); }}Copy the code

##DiskLruCache retrieve data ##

    privateResource<? > decodeFromCache()throwsException { Resource<? > result =null;
        try {
            result = decodeJob.decodeResultFromCache();
        } catch (Exception e) {
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "Exception decoding result from cache: "+ e); }}if (result == null) {
            result = decodeJob.decodeSourceFromCache();
        }
        return result;
    }Copy the code

Then call decodeResultFromCache in the decodeJob class

 public Resource<Z> decodeResultFromCache(a) throws Exception {
        if(! diskCacheStrategy.cacheResult()) {return null;
        }

        long startTime = LogTime.getLogTime();
        // Fetch resources from DiskCache
        Resource<T> transformed = loadFromCache(resultKey);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Decoded transformed from cache", startTime);
        }
        startTime = LogTime.getLogTime();
        Resource<Z> result = transcode(transformed);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Transcoded transformed from cache", startTime);
        }
        return result;
    }
    // Fetch resources from DiskCache
    private Resource<T> loadFromCache(Key key) throws IOException {
    // Obtain files from DiskCache according to the key
        File cacheFile = diskCacheProvider.getDiskCache().get(key);
        if (cacheFile == null) {
            return null;
        }

        Resource<T> result = null;
        try {
            result = loadProvider.getCacheDecoder().decode(cacheFile, width, height);
        } finally {
            if (result == null) { diskCacheProvider.getDiskCache().delete(key); }}return result;
    }Copy the code

Next we analyze the decodeFromSource method

// Call decodeJob to get and encode data
    privateResource<? > decodeFromSource()throws Exception {
        return decodeJob.decodeFromSource();
    }
    public Resource<Z> decodeFromSource(a) throws Exception {
    // Get data, decode
        Resource<T> decoded = decodeSource();
        // Encode and save
        return transformEncodeAndTranscode(decoded);
    }
 // Get data, decode
    private Resource<T> decodeSource(a) throws Exception {
        Resource<T> decoded = null;
        try {
            long startTime = LogTime.getLogTime();
            // Data pull
            final A data = fetcher.loadData(priority);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Fetched data", startTime);
            }
            if (isCancelled) {
                return null;
            }
            / / code
            decoded = decodeFromSourceData(data);
        } finally {
            fetcher.cleanup();
        }
        return decoded;
    }Copy the code

LoadData () of DataFetcher is first called to pull data. There are several classes for DataFetcher implementation. Let’s take fetching data from URL as an example, namely HttpUrlFetcher class

   @Override
    public InputStream loadData(Priority priority) throws Exception {
        return loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/.null /*lastUrl*/, glideUrl.getHeaders());
    }
// Return the InputStream object
    private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers)
            throws IOException {
        if (redirects >= MAXIMUM_REDIRECTS) {
            throw new IOException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
        } else {
            // Comparing the URLs using .equals performs additional network I/O and is generally broken.
            // See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
            try {
                if(lastUrl ! =null && url.toURI().equals(lastUrl.toURI())) {
                    throw new IOException("In re-direct loop"); }}catch (URISyntaxException e) {
                // Do nothing, this is best effort.}}// Static factory creates an HttpURLConnection object
        urlConnection = connectionFactory.build(url);
        for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
          urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
        }
        // Set the request parameters
        // Set the connection timeout to 2500ms
        urlConnection.setConnectTimeout(2500);
        // Set the read timeout to 2500ms
        urlConnection.setReadTimeout(2500);
        // Do not use HTTP caching
        urlConnection.setUseCaches(false);
        urlConnection.setDoInput(true);

        // Connect explicitly to avoid errors in decoders if connection fails.
        urlConnection.connect();
        if (isCancelled) {
            return null;
        }
        final int statusCode = urlConnection.getResponseCode();
        if (statusCode / 100= =2) {
        // The request succeeded
            return getStreamForSuccessfulRequest(urlConnection);
        } else if (statusCode / 100= =3) {
        //
            String redirectUrlString = urlConnection.getHeaderField("Location");
            if (TextUtils.isEmpty(redirectUrlString)) {
                throw new IOException("Received empty or null redirect url");
            }
            URL redirectUrl = new URL(url, redirectUrlString);
            return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
        } else {
            if (statusCode == -1) {
                throw new IOException("Unable to retrieve response code from HttpUrlConnection.");
            }
            throw new IOException("Request failed " + statusCode + ":"+ urlConnection.getResponseMessage()); }}Copy the code

Seeing that this finally looks at the network load request, we can also customize the DataFetcher to use other network libraries such as OkHttp, Volley. Finally, we look at transformEncodeAndTranscode method

 private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {
        long startTime = LogTime.getLogTime();
        // Calculate the size of the image to be used by the ImageView according to the ImageView scaleType parameters and save the true width and height of the image.
        Resource<T> transformed = transform(decoded);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Transformed resource from source", startTime);
        }
  // Write to DiskLruCache and use it directly from DiskLruCache next time
        writeTransformedToCache(transformed);

        startTime = LogTime.getLogTime();
          // Transcode the source image to the image format required by the ImageView
        Resource<Z> result = transcode(transformed);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Transcoded transformed from source", startTime);
        }
        return result;
    }Copy the code

At this point, image loading process is introduced, and of course a lot of places did not mention, believe that if you’re reading this article, at the same time, his tracking source will be a lot easier, if you don’t jump on the source, may see this article a few times to Glide principle understanding is fog, or see understand at the time, but don’t remember soon. So remember to follow the source code again.

This article is really long, can finish this article also need a certain perseverance… If the article is inadequate or wrong, welcome to correct, in order to prevent other readers wrong guidance.

Welcome to follow my CSDN and micro blog