preface

Glide version 4.11.0 is used for this source analysis, and it is one of the few complex source frameworks I have looked at in detail. The last time I looked at it was when I looked at ARouter in my sophomore year, but it was very limited at that time (I don’t know if it is any better now). The source code analysis of 4-5 days or so, their own practice Glide some of the functions + read documents + see the source code, and then summarized this reading notes, also does fill some of my knowledge gaps. This reading notes content is more detailed, and with notes + pictures, reading friends should also be easier to understand.

This paper is divided into the following parts, aiming at our common Glide.with().load().into() writing method, to the network request as an example of analysis. After I finished writing this article, I also looked at some of the Glide surface, a little sorting and thinking can basically answer the question. If you think it’s a good idea, please give it a thumbs up. If you have any questions, we can discuss them in the comments section

Part I Request foreshadowing

General usage

Glide is used in daily development to load image resources into ImageView or its subclasses, or to download images using Glide. This article first load a picture in the network to ImageView for example analysis Glide source. The emphasis is on first time because Glide has an image reuse strategy.

Glide. With (this) // return RequestManager object. Load (imagePath) // Return RequestBuilder object. Into (imageView) // Green. With (this).load(imagePath).placeholder(r.color.black) // Return BaseRequestOptions .error(r.lawable.ic_launcher_background) // Return BaseRequestOptions. Transform (MultiTransformation(FitCenter(), CenterCrop ())). DiskCacheStrategy (diskCacheStrategy. RESOURCE) into (imageView) / / here you can also specify a Target val resultBitmap = Glide.with(this) .asBitmap() .load(imagePath) .submit() .get()Copy the code

Consider the following questions:

  1. Do I need to pay attention to thread switching when USING Glide?
  2. As a streaming call, it’s hard not to think of intermediate operations and termination operations in things like RxJava Kotlin. Can streaming calls to Glide be divided into “intermediate operations” and “termination operations”?
  3. How about the realization of Glide’s cache mechanism? How does Glide reuse resources?

Glide construction method

Glide usually starts with a Glide singleton and begins the request loading process after Glide. With (context). So let’s first look at the constructors in Glide.

// Glide#constructor(() 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 RequestOptionsFactory defaultRequestOptionsFactory, @NonNull Map<Class<? >, TransitionOptions<? ,? >> defaultTransitionOptions, @NonNull List<RequestListener<Object>> defaultRequestListeners, boolean isLoggingRequestOriginsEnabled, Boolean isImageDecoderEnabledForBitmaps) {/ / initializes the global variable / / such as this. Engine = engine... final Resources resources = context.getResources(); Registry = new Registry(); registry.register(new DefaultImageHeaderParser()); // Right now we're only using this parser for HEIF images, which are only supported on OMR1+. // If we need this for other file types, we should consider removing this restriction. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { registry.register(new ExifInterfaceImageHeaderParser()); } List<ImageHeaderParser> imageHeaderParsers = registry.getImageHeaderParsers(); // Initialize the classes associated with image sampling decoding..... // Different types of data are given to different types of factory classes for initialization and processing. ResourceDrawableDecoder = new ResourceDrawableDecoder(context); ResourceLoader.StreamFactory resourceLoaderStreamFactory = new ResourceLoader.StreamFactory(resources); . // There are different decoders for different types of images (Bitmap files). These decoders are registered in Registry...... ImageViewTargetFactory ImageViewTargetFactory = new ImageViewTargetFactory(); GlideContext = new GlideContext(context, arrayPool, registry, imageViewTargetFactory, defaultRequestOptionsFactory, defaultTransitionOptions, defaultRequestListeners, engine, isLoggingRequestOriginsEnabled, logLevel); }Copy the code

Glide constructor signature, an important class involved in the constructor.

type role
Engine Manage and load active and cache resources
MemoryCache The resource implementation is cached using the LRU caching algorithm
BitmapPool Recycle and reuse Bitmap resources
ArrayPool Recycle and reuse resources
RequestManagerRetriever Get the RequestManager object
RequestOptionsFactory The factory class for RequestOptions
RequestRegistry Decode encoder Transcoder and other registration management
GlideContext A global Glide context object that provides the parameters needed to load the resource

Acquisition of Glide singleton

// Glide#get public static Glide get(@nonnull Context Context) {if (Glide == null) {// About the GlideModule..... synchronized (Glide.class) { if (glide == null) { checkAndInitializeGlide(context, annotationGeneratedModule); } } } return glide; }Copy the code

Implementation of Bitmap reuse and recycling

Bitmap reuse and recycling rely primarily on BitmapPool objects. In Glide construction, BitmapPool works mainly with encoder, decoder, Transcoder, DownSampler and other image-processing classes. Since I’m not interested in image processing for the time being, I’ll take a look at the BitmapPool logic for managing bitmaps.

LruBitmapPool

The main implementation class of BitmapPool is LruBitmapPool, a class that uses the Lru algorithm to manage Bitmap objects. According to the idea of the Lru algorithm, a new Bitmap object is added to the list, and the list size exceeds maxSize, and the least frequently used Bitmap is removed:

// LruBitmapPool#put(Bitmap) public synchronized void put(Bitmap bitmap) { if (bitmap == null) { throw new NullPointerException("Bitmap must not be null"); } // Native methods are used to clean up the pixels pointed to by Bitmap references when bitmap.recycle () is called. The recycle method is used primarily as a tag to facilitate GC collection. Return true if (bitmap.isrecycled ()) {throw new IllegalStateException("Cannot pool recycled ") bitmap"); } // isMutable() indicates that bitmaps can be reused if (! bitmap.isMutable() || strategy.getSize(bitmap) > maxSize || ! allowedConfigs.contains(bitmap.getConfig())) { //.... // If: // 1. The current Bitmap cannot be reused // 2. The current Bitmap size is too large (bytes exceeds maxSize) // 3. Bitmap.recycle () does not meet the relevant Bitmap Settings. return; } final int size = strategy.getSize(bitmap); strategy.put(bitmap); tracker.add(bitmap); puts++; currentSize += size; / /... dump(); evict(); }Copy the code

A related class, ArrayPool, has a similar idea.

Glide.with()

RequestManager and RequestManagerRetriever

Glide. With () returns a RequestManager object that is available through the RequestManagerRetriever. Before looking at the actual code, I’ve summarized the responsibilities of these two classes:

  • RequestManager responsibilities:
    1. Listen for the Activity to declare periodic callbacks
    2. Manage and start Request
  • RequestManagerRetriever responsibilities:
    1. Return RequestManager objects via different overloaded functions

Return the RequestManager object using the RequestManagerRetriever

Listen for the lifecycle through the RequestManagerFragment

This is mainly done through a series of overloaded functions called get(), as shown in the figure below: RequestManager#get(Activity) and RequestManager#get(Context) are selected for demonstration

RequestManager#get(Activity) public RequestManager get(@nonnull Activity Activity) {// If in the UI thread: / / whether the UI thread here is judged through the stars, if the incoming context is nature does not "UI thread" ApplicationContext / / the if (Util. IsOnBackgroundThread ()) {/ / - note 2:  RequestManager#get(Context) --- return get(activity.getApplicationContext()); } else { assertNotDestroyed(activity); android.app.FragmentManager fm = activity.getFragmentManager(); // -- Note 1: Return fragmentGet(Activity, FM, /*parentHint=*/ null, isActivityVisible(Activity)); }}Copy the code

Generally speaking, we do not need to register Glide.clear() in the Activity’s lifecycle callback when we use Glide, This is because the RequestManager returned by Glide itself through the RequestManagerRetriever needs to have the ability to listen for lifecycle callbacks. The goal of this implementation is for Glide to be able to easily recycle resources. When an Activity is destroyed, no additional loading is required, and no loaded Bitmap is required to consume resources in memory.

@override public void onStart() {super.onstart (); / / lifeCycle is ActivityFragmentLifecycle instance, used to prompt the RMFragment binding Activity lifeCycle callback lifeCycle. The onStart (); }Copy the code

The listening approach is implemented by binding the RequestManager to a viewless Fragment: RequestManagerFragment (RMFragment).

// -- Note 1: - Private RequestManager fragmentGet(@nonNULL Context Context) - Private RequestManager fragmentGet(@nonNULL Context Context) @NonNull android.app.FragmentManager fm, @Nullable android.app.Fragment parentHint, boolean isParentVisible) { // view-less RMFragment RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible); RequestManager requestManager = current.getRequestManager(); if (requestManager == null) { // TODO(b/27524013): Factor out this Glide.get() call. Glide glide = Glide.get(context); / / bind RMFragment requestManager = factory. Build (glide, current getGlideLifecycle (), current.getRequestManagerTreeNode(), context); current.setRequestManager(requestManager); } return requestManager; }Copy the code

Inside the RMFragment, it implements listening for the Activity life cycle that it comes from. Requestmanagers are built with Factory so they have lifecycle awareness.

If a context is passed in, go to get(context) for note 2:

// -- Note 2:  RequestManager#get(Context) --- // RequestManagerRetriver#get(context) public RequestManager get(@NonNull Context context) { if (context == null) { throw new IllegalArgumentException("You cannot start a load on a null Context"); } else if (util.isonMainThread () &&! (context instanceof Application)) { if (context instanceof FragmentActivity) { return get((FragmentActivity) context); } else if (context instanceof Activity) { return get((Activity) context); } else if (context instanceof ContextWrapper // Only unwrap a ContextWrapper if the baseContext has a non-null application context. // Context#createPackageContext may return a Context without an Application instance, // in which case a ContextWrapper may be used to attach one. && ((ContextWrapper) context).getBaseContext().getApplicationContext() ! = null) { return get(((ContextWrapper) context).getBaseContext()); }} // Use the singleton to return a RequestManager object. }Copy the code

RequestManagerRetriver#get(Context) The main idea is to type match all context instances. The main cases are divided into contexts with a UI and contexts without a UI, the latter being the application context. For such a context, the RequestManagerRetriever returns an applicationManager singleton:

// RequestManagerRetriever#getApplicationManager private RequestManager getApplicationManager(@NonNull Context context) { // Either an application context or we're on a background thread. if (applicationManager == null) { synchronized (this) { if (applicationManager == null) { //..... Glide glide = Glide.get(context.getApplicationContext()); applicationManager = factory.build( glide, new ApplicationLifecycle(), new EmptyRequestManagerTreeNode(), context.getApplicationContext()); }}}Copy the code

If it’s a UI-related context, you would build a Glide instance every time, which is not hard to understand because uI-related contexts have a life cycle.

// RequestManagerRetriever#supportFragmentGet private RequestManager supportFragmentGet(....) {/ /... if (requestManager == null) { //... Glide Glide = Glide. Get (context); requestManager = factory.build( glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context); current.setRequestManager(requestManager); } return requestManager; }Copy the code

Glide.with().asBitmap().load()

It’s important to note that Glide. With () returns a RequestManager object, while asBitmap(), Load (), and Transform () all return a RequestBuilder object.

RequestBuilder

RequestBuilder itself is a subclass of BaseRequestOptions

RequestBuilder<TranscodeType> extends BaseRequestOptions<RequestBuilder<TranscodeType>>
Copy the code

Transform (), Override (),placeholder() are methods that RequestBuilder inherits from its parent class. The rationale for building this parent-child class was also, I think, the need to keep the RequestBuilder focused on its single responsibility. RequestBuilder’s main responsibilities:

  1. Convert the resource type through various asXXX: asBitmap(), asFile()
  2. Submit (); submit(); submit()
  3. Into ()

You can also use transistion to make transitions.

Load dependent method

The RequestBuilder#load method has many overloaded functions, all of which are more or less the same. In fact, the load method does not do any substantial work, but for the load(Model) parameters accepted by the model, will be converted into Object class storage. Here’s a simple example:

// RequestBuilder#load(File file) public RequestBuilder<TranscodeType> load(@Nullable File file) { return loadGeneric(file); } // RequestBuilder#loadGeneric private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) { this.model =  model; isModelSet = true; return this; }Copy the code

RequestOptions

BaseRequestOptions

RequestBuilder request Request request request request request request request request request request request request request request

BaseRequestOptions#centerCrop() public T centerCrop() {BaseRequestOptions#centerCrop() public T centerCrop() {return transform(DownsampleStrategy.CENTER_OUTSIDE, new CenterCrop()); }Copy the code

Glide.with().load().into()

The actual method to start loading is placed in into. The into method is generally the focus of our common operations, and there are many overloads of into methods in the RequestBuilder. Let’s take a look at two commonly used ones:

Public ViewTarget<ImageView, public ViewTarget<ImageView, TranscodeType> into(@nonnull ImageView view) {// Must be on the main thread util.assertMainThread (); Preconditions.checkNotNull(view); BaseRequestOptions<? > requestOptions = this; / /... return into( glideContext.buildImageViewTarget(view, transcodeClass), /*targetListener=*/ null, requestOptions, Executors.mainThreadExecutor()); } // 2. RequestBuilder#into(Target) Public <Y extends Target<TranscodeType>> Y into(@nonnull Y Target) {return into(Target, / * targetListener = * / null, / / the specified callback thread is given priority to the thread Executors. MainThreadExecutor ()); }Copy the code

There are other methods of overloading, but the general operation and ideas are similar. Each of these overloaded methods specifies the Executor thread as the main thread, and each of these methods will eventually call this overload if it leads to Rome:

private <Y extends Target<TranscodeType>> Y into( @NonNull Y target, @Nullable RequestListener<TranscodeType> targetListener, BaseRequestOptions<? > options, Executor callbackExecutor) { Preconditions.checkNotNull(target); if (! isModelSet) { throw new IllegalArgumentException("You must call #load() before calling #into()"); } // 1. CallbackExecutor Request Request = buildRequest(target, targetListener, options, callbackExecutor); Request previous = target.getrequest (); if (request.isEquivalentTo(previous) && ! isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) { if (! Preconditions.checkNotNull(previous).isRunning()) { // 3. Begin previous.begin(); } return target; } // 4. If the Tracker cannot be reused, bind it to the build request // TargetTracker is related to the lifecycle callback. requestManager.clear(target); target.setRequest(request); // Begin requestManager. Track (target, request); return target; }Copy the code

The following conclusions can be drawn from into’s code

  1. Request is tracked by TargetTrack. TargetTracker implements LifecycleListener and listens for lifecycle callbacks.
  2. The Request object is bound to the Target object. The Request for the same Target is the same.
  3. Request of binding callbackExecutor, namely implement the callback after the completion of the thread pool is Executors mainThreadExecutor ().

Target

The returned Target object is also important, and Glide can manipulate the loaded resource Settings into the ImageView through callbacks. For example, an implementation of Target, ImageViewTarget:

// ImageViewTarget#onResourceReady @Override public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) { if (transition == null || ! Transition. Transition (resource, this)) {// Set the resource to ImageView setResourceInternal(resource); } else { maybeUpdateAnimatable(resource); }}Copy the code

When the onResourceReady callback is triggered, it eventually goes to a subclass of BitmapImageViewTarget (ImageViewTarget) to set the Bitmap:

// BitmapImageViewTarget#setResource
@Override
protected void setResource(Bitmap resource) {
  view.setImageBitmap(resource);
}
Copy the code

SingleRequest#begin

From the into method above we go to begin of SingleRequest:

// 3. Begin previous.begin();Copy the code

As an interface, one of the implementation classes of Request is SingleRequest. The SingleRequest is responsible for loading the Resource into the Target. SingleRequest defines a series of enumeration methods to identify the loading status of the current resource:

// The Request has been created, but no PENDING is being executed. // The Request has been created, but no PENDING is being executed. WAITING_FOR_SIZE, // the image is COMPLETE, // FAILED, // the Request is cleared with placeholder placeholder, In this case, you might need to restart the Request CLEARED,Copy the code

Understanding what these enumerations mean makes it easier to read begin’s source code:

// SingleRequest#begin public void begin() { synchronized (requestLock) { assertNotCallingCallbacks(); stateVerifier.throwIfRecycled(); startTime = LogTime.getLogTime(); / /... Cannot begin The RUNNING Request if (status == status. RUNNING) {throw new IllegalArgumentException("Cannot restart a RUNNING request"); If (status == Status.COMPLETE) {// onResourceReady checks the Resource. OnResourceReady (Resource, DataSource.MEMORY_CACHE); return; } status = Status.WAITING_FOR_SIZE; if (Util.isValidDimensions(overrideWidth, overrideHeight)) { // 3. OnSizeReady (overrideWidth, overrideHeight); } else { target.getSize(this); } / /... }}Copy the code

Loading an image on the web using Glide is one of our most common scenarios, and this process is handled by the Engine#load method. Note the following parameters:

Engine.load (// a variable of type GlideContext that manages a lot of load configuration information GlideContext, // model Object type model, //..... // ResourceCallback is a callback after the resource is loaded. SingleRequest implements this. This means that resources load after good will control back to SingleRequest methods in this, / / Executors mainThreadExecutor callbackExecutor ());Copy the code

The overall call chain is very long, and I’ve summarized it in this picture, so let’s look at it in detail.

Part II loading starts

Engine#load

The next stop after Request#begin is back to Engine#load

The most classic aspect of Glide is that it breaks the cache model into three parts:

  1. Active Resources – Is there another View showing this image right now?
  2. LRU Memory cache – has the image been loaded recently and still exists in Memory?
  3. Resource Type – Has this image been decoded, converted, and written to disk cache before?
  4. Data Source – Whether the resource used to build this image has previously been written to the file cache

Cache in Glide – official documentation

The last two parts are the hard disk cache which we can view as one part; The first and second are all in memory, which we call memory cache.

Glide first looks to see if such a resource exists in the memory cache (active cache and Lru cache), and if it is not requested on the network. Glide’s handling of the memory cache is discussed in more detail in the following sections, focusing first on the network request process.

Specific corresponding functions are shown in the table:

Contains Engine# load from the memory to find (loadFromMemory) and find (waitForExistingOrStartNewJob) from the network. First take a look at Engine# load

public <R> LoadStatus load(......) {/ /... // buildKey EngineKey = keyfactory.buildkey (........) ; // Find if there is such a resource in memory EngineResource<? > memoryResource; synchronized (this) { memoryResource = loadFromMemory(key, isMemoryCacheable, startTime); If (memoryResource = = null) {/ / if not, go to the Internet to find the return waitForExistingOrStartNewJob (... ; }}Copy the code

Engine#waitForExistingOrStartNewJob

Let’s assume that we are now loading for the first time, and the resources we need do not exist in the memory cache or hard disk cache. We need to load resources from the network.

// Engine#waitForExistingOrStartNewJob private <R> LoadStatus waitForExistingOrStartNewJob(....) {// If EngineJob exists EngineJob<? > current = jobs.get(key, onlyRetrieveFromCache); if (current ! = null) { current.addCallback(cb, callbackExecutor); / /... return new LoadStatus(cb, current); } EngineJob<R> engineJob = engineJobFactory.build(....) ; DecodeJob<R> decodeJob = decodeJobFactory.build(.....) ; jobs.put(key, engineJob); engineJob.addCallback(cb, callbackExecutor); DecodeJob enginejob. start(decodeJob); / / /... return new LoadStatus(cb, engineJob); }Copy the code

EngineJob Engine# waitForExistingOrStartNewJob will first see if exists. The basis of existence is to see if the same key exists in the Map. The EngineKey has a number of EngineKey parameters. If the EngineKey is changed slightly, it will be treated as a different Key and cause a re-request

EngineKey key =
    keyFactory.buildKey(
        model,
        signature,
        width,
        height,
        transformations,
        resourceClass,
        transcodeClass,
        options);
Copy the code

The same EngineJob can be reused. If not reusable, execute DecodeJob using thread pool:

EngineJob#start

public synchronized void start(DecodeJob<R> decodeJob) { this.decodeJob = decodeJob; / / choice from the disk or decoding / / if it is from the activity resources in subsequent requests, and in the disk cache, it will take from the disk GlideExecutor executor. = decodeJob willDecodeFromCache ()? diskCacheExecutor : getActiveSourceExecutor(); // The run method executor. Execute (decodeJob) is finally called; }Copy the code

DecodeJob#run

The thread pool eventually calls the run method in the DecodeJob. Before analyzing the specific code, you need to understand DecodeJob’s responsibilities:

The duties of a DecodeJob

A class responsible for decoding resources either from cached data or from the original source and applying transformations and transcodes.

DecodeJob DecodeJob decodes caches and resources from the original source, and performs transitions and transcodes, according to the document.

class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback, Runnable, Comparable<DecodeJob<? >>, PoolableCopy the code

DecodeJob decodes resources that should also be in the call chain of the run method:

DecodeJob#run public void run() {DecodeJob#run public void run() { > localFetcher = currentFetcher; try { if (isCancelled) { notifyFailed(); return; } runWrapped(); } Catch (CallbackException e) {// Exception and handler}}Copy the code

Down the call chain, we end up with the SourceGenerator#startNext method. Let’s take a quick look at the source code:

SourceGenerator#startNext

Return true public Boolean startNext() {//..... sourceCacheGenerator = null; loadData = null; boolean started = false; while (! started && hasNextModelLoader()) { // 1. Build LoadData from ModelLoader // -- Remarks 1 ---- Glide How to select a specific ModelLoader object? loadData = helper.getLoadData().get(loadDataListIndex++); if (loadData ! = null && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource()) || helper.hasLoadPath(loadData.fetcher.getDataClass()))) { started = true; // 2. Perform the actual request process startNextLoad(loadData); } } return started; }Copy the code

The core idea of startNext is to use a while to find the appropriate ModelLoader to set up LoadData, so it completes the process of a request by introducing the following roles:

  1. DataFetcher
  2. ModelLoader

DataFecther, as its name implies, is an important player in completing network requests, as discussed in more detail below. First look at ModelLoader:

ModelLoader

Glide designed the ModelLoader to convert complex data types to LoadData handled by DataFetcher. A ModelLoader is essentially a factory that creates LoadData. HttpGlideUrlLoader is the ModelLoader implementation class that implements the associated methods:

public class HttpGlideUrlLoader implements ModelLoader<GlideUrl, InputStream>
Copy the code

The purpose is purely to convert the GlideURl-wrapped String URL object into an InputStream that the Decoder can handle.

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

Matching of ModelLoader objects

Let’s go back to Note 1 above to see how Glide matches the corresponding ModelLoader object:

// -- Remarks 1 ---- Glide how to select a specific ModelLoader object? loadData = helper.getLoadData().get(loadDataListIndex++);Copy the code
List<LoadData<? >> getLoadData() { // ..... GlideContext is the management context for all related objects. GlideContext is created in Glide constructor. List<ModelLoader<Object,? >> modelLoaders = glideContext.getRegistry().getModelLoaders(model); for (int i = 0, size = modelLoaders.size(); i < size; i++) { ModelLoader<Object, ? > modelLoader = modelLoaders.get(i); // 2. The buildLoadData method described above builds LoadData objectloadData <? > current = modelLoader.buildLoadData(model, width, height, options); / /... } } return loadData; }Copy the code

The Match for ModelLoader continues down the call chain, and the result can be found in ModelLoaderRegistry

public <A> List<ModelLoader<A, ? >> getModelLoaders(@NonNull A model) { List<ModelLoader<A, ? >> modelLoaders = getModelLoadersForClass(getClass(model)); / / /... for (int i = 0; i < size; i++) { // ... If (loader.handles(Model)) if (loader.handles(Model)) if (loader.handles(Model)) }Copy the code

SourceGenerator#startNextLoad

After analyzing SourceGenerator#startNext, we follow the main thread and come to the actual method to start load: startNextLoad

// SourceGenerator#startNextLoad

private void startNextLoad(final LoadData<?> toStart) {
  loadData.fetcher.loadData(
      helper.getPriority(),
      new DataCallback<Object>() {
        @Override
        public void onDataReady(@Nullable Object data) {
          if (isCurrentRequest(toStart)) {
            onDataReadyInternal(toStart, data);
          }
        }

        @Override
        public void onLoadFailed(@NonNull Exception e) {
          if (isCurrentRequest(toStart)) {
            onLoadFailedInternal(toStart, e);
          }
        }
      });
}
Copy the code

The Fetcher object bound to LoadData is used to fetch data, and the callback is performed for both success and failure. That brings us to our final section, which uses HttpUrlFetcher:

HttpDataFetcher#loadData

// HttpDataFetcher#loadData public void loadData( @NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) { long startTime = LogTime.getLogTime(); InputStream result = loadDataWithRedirects(glideurl.tourl (), 0, null, glideurl.getheaders ()); Callback is an implementation of DataCallback. This method is implemented by the anonymous inner class in // SourceGenerator above. } catch (IOException e) { // .... // Exception callback callback. OnLoadFailed (e); } finally { // ... }}}Copy the code

Redirects may occur during image request, and the LoadData method here is actually just a wrapper of LoadData withredirects. The specific request process is as follows:

HttpUrlFetcher#loadDataWithRedirects

// use HttpUrlFetcher#loadDataWithRedirects to load and output InputStream private InputStream loadDataWithRedirects( URL url, int redirects, URL lastUrl, Map<String, String> headers) throws IOException {// Do not have too many redirects. Redirects will be called recursively. Throw new HttpException("Too many (> "+ MAXIMUM_REDIRECTS +)  ") redirects!" ); } else { // ...... urlConnection = connectionFactory.build(url); / /... Omitted some configuration code for HttpUrlConnection // urlConnection.connect(); / / get the input stream stream = urlConnection. GetInputStream (); if (isCancelled) { return null; } / /... / / if return a status code of success if (isHttpOk (statusCode)) {/ / to get an InputStream return getStreamForSuccessfulRequest (urlConnection); } else if (isHttpRedirect(statusCode)) {redirectUrlString = redirectUrlString urlConnection.getHeaderField("Location"); if (TextUtils.isEmpty(redirectUrlString)) { throw new HttpException("Received empty or null redirect url"); } URL redirectUrl = new URL(url, redirectUrlString); Inputstream.close () urlconnection.disconnect () cleanup(); Return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers); } else if (statusCode == INVALID_STATUS_CODE) { throw new HttpException(statusCode); } else { throw new HttpException(urlConnection.getResponseMessage(), statusCode); }}Copy the code

When the request is successful, Glide calls back to SourceGenerator’s anonymous inner class Callback to decode the returned InputStream. I’m not going to analyze the details here,

To summarize

The above request process is quite long and a bit complex, so I use a diagram to summarize the main call logic in the process:

The entry to the request is RequestBuilder#into, which starts the request with Request#begin. The Request is first marked as PENDING, meaning that the Request is built but not yet started. Engine#load determines whether to load resources in memory (active Cache resources), disk, or start a new task. When ready, the child thread is started to process the task and goes back to BEGIN. The current request status is RUNNING. Then we wait for Target to wait for the result, and we can set the ImageView; The placeholder.

Glide cache analysis

The matching strategy of two levels of memory cache

LoadFromMemory () first looks for the resource in ActiveResource, and then in Cache if it is not present in Active. The relationship between the two caches is shown in the figure below:

Case#2: if the active resource does not exist, take the resource from the Cache, and “activate” the resource to the active resource.

Reference counting is used for both active and LRU resources. When the Acquired counter becomes 0, the resources in the active resource are rolled back into the LRU resource.

synchronized void acquire() {
 // ..... 
  ++acquired;
}
Copy the code

The reason for the design of such a two-level cache on the memory cache is my personal understanding: if there is only an Lru cache, the least frequently used resource, 1 in the figure, will be removed as later resources are added, and if needed, it will need to be re-requested. The purpose of designing an active resource is to indicate that the resource is in use and can be removed from the LRU when needed, reducing the strain on the LRU cache.

loadFromMemory

// Engine#loadFromActiveResources(key) private EngineResource<? > loadFromActiveResources(Key Key) {// activeEngineResource is used for Resource retrieval // Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>() > active = activeResources.get(key); if (active ! = null) { active.acquire(); } return active; } // Engine#loadFromCache(key) private EngineResource<? > loadFromCache(Key key) { EngineResource<? > cached = getEngineResourceFromCache(key); if (cached ! = null) { cached.acquire(); Activeresources. activate(key, cached); } return cached; }Copy the code

The activeResources in Engine#loadFromActiveResources(key) is an instance of an ActiveResource. ActiveResource uses a Map to bind Key and Resource

@VisibleForTesting final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>(); class ResourceWeakReference extends WeakReference<EngineResource<? >>Copy the code

The object in ResourceWeakReference is generally a Bitmap. Such operation is to conveniently reclaim the Bitmap modified by weak reference when memory is insufficient. For example, when a resource in an Lru is activated to an active resource, the reset() method is called to set the reference to null for GC collection.

// ResourceWeakReference#reset
void reset() {
  resource = null;
  // native clear
  clear();
}
Copy the code

GetEngineResourceFromCache () MemoryCache is introduced in the concrete implementation. MemoryCache uses the LRU algorithm to manage the objects it puts in, and when the number of objects exceeds a specified size, the least frequently used objects are discarded.

public class LruResourceCache extends LruCache<Key, Resource<? >> implements MemoryCacheCopy the code

Glide’s disk caching strategy

Caching strategies meaning
ALL Data and Resource are cached for remote images, and only Resource is cached for local images
AUTOMATIC Read downloaded raw data from disk
DATA Only the cache Data
NONE Neither Data nor Resource is cached
RESOURCE Only the cache the Resource

In my understanding, Data refers to the original Data that has not been processed, while Resource refers to the Data after processing. Glide’s default policy is AUTOMATIC, which loads raw images that have not been cropped or transformed. After loading an image requested from the network, if the cache can be “found” in the disk cache, it will be displayed from the cache. As mentioned above, the EngineKey is first constructed in the Engine#load method. The EngineKey is the identifier of the file and consists of the following contents:

EngineKey key =
    keyFactory.buildKey(
        model,
        signature,
        width,
        height,
        transformations,
        resourceClass,
        transcodeClass,
        options);
Copy the code

Here I Debug to see what the parameters look like in the current example.The key is very strict, and the parameters such as length and width, model (url) configuration change will cause the EngineKey to be different, so it can’t load images from the disk cache. Disk caching is also implemented through the DiskLruCache class supported by the Lru algorithm.

If there is a cache on disk, in the EngineJob#start method diskExecutor is used to load it from disk:

public synchronized void start(DecodeJob<R> decodeJob) {
  this.decodeJob = decodeJob;
  GlideExecutor executor =
      decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor();
  executor.execute(decodeJob);
}
Copy the code

conclusion

  1. Glide.with().load().into().glide.load().into(
  2. Glide returns a global singleton to the Application Context. For each UI Context, it first checks to see if there is a created Glide singleton, and if not, it creates and returns a UI-bound singleton.
  3. Glide loads cache, disk resources using a child thread pool, and in the process passes the callback thread pool to the master thread pool. There is no thread switch, and the loaded resource is placed on the ImageView by calling back to set the image in Target.

Reference # Let’s talk about Glide in the interview

# Glide source code analysis – caching and reuse mechanisms