SoFunction
Updated on 2025-03-11

In-depth analysis of the Gilde source code layer of Android image loading framework

1. Use Gilde to display an image

(this).
        load("/sa/simg/hpb/")
        .into(imageView);

The above is the simplest way to display a picture of Glide. Although there are only three steps: with, load, and into, gilde is maintained through a large amount of code.

with: Return a RequestManager

load: Return a RequestBuilder

The following is analyzing these three parts through source code, focusing on the management and cache use of Glide's biotechnology cycle, as well as the sorting out the entire loading process.

with operation source code analysis

Glide's with function has multiple overloads due to parameter type. The following is to select the activity to pass it as the parameter.

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

This method is to get a RequestManager

Analyze getRetriever

private static RequestManagerRetriever getRetriever(@Nullable Context context) {
  (
      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 (context).getRequestManagerRetriever();
}

Get a RequestManagerRetriever, this manages RequestManager

RequestManagerRetriever get method

public RequestManager get(@NonNull Context context) {
  if (context == null) {
    throw new IllegalArgumentException("You cannot start a load on a null Context");
  } else if (() && !(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());
    }
  }
  return getApplicationManager(context);
}

Different code logic is executed according to different parameter types.

The parameter is: the source code of FragmentActivity

public RequestManager get(@NonNull FragmentActivity activity) {
  if (()) {
    return get(());
  } else {
    assertNotDestroyed(activity);
    (activity);
    FragmentManager fm = ();
    return supportFragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
  }
}

Note that the thread is judged here. If it is a child thread, it will directly overload the function of ApplicationContext type.

SupportFragmentGet method:

private RequestManager supportFragmentGet(
    @NonNull Context context,
    @NonNull FragmentManager fm,
    @Nullable Fragment parentHint,
    boolean isParentVisible) {
  SupportRequestManagerFragment current = getSupportRequestManagerFragment(fm, parentHint);
  RequestManager requestManager = ();
  if (requestManager == null) {
    // TODO(b/27524013): Factor out this () call.
    Glide glide = (context);
    requestManager =
        (
            glide, (), (), context);
    // This is a bit of hack, we're going to start the RequestManager, but not the
    // corresponding Lifecycle. It's safe to start the RequestManager, but starting the
    // Lifecycle might trigger memory leaks. See b/154405040
    if (isParentVisible) {
      ();
    }
    (requestManager);
  }
  return requestManager;
}

This method will create a blank SupportRequestManagerFragment. The role of this Fragment is very important and is also the core of Glide. The management of declaration cycles is monitored by this.

Monitored: onStart, onStop, onDestroy, so when Glide is used, we don’t have to care about the release of resources, because Glide will automatically release resources, and the basis for releasing resources is the management of this declaration cycle.

The get function parameter is: the source code of ApplicationContext

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 = (());
        applicationManager =
            (
                glide,
                new ApplicationLifecycle(),
                new EmptyRequestManagerTreeNode(),
                ());
      }
    }
  }
  return applicationManager;
}

It can be seen that when the parameter type is: ApplicationContext or the child thread calls the with method, it will not create a blank Fragmnet for life cycle monitoring, nor will it release resources, so the ApplicationContext cannot be transmitted when calling the with method.

The following analysis shows which logic Glide executes in onStart, onStop, and onDestroy respectively

RequestManager's onStart method

public synchronized void onStart() {
  resumeRequests();
  ();
}

Execute the refresh request and the onStart of the Tracker, resumeRequests, and the cache of the request.

RequestTracker resumeRequests method

public void resumeRequests() {
  isPaused = false;
  for (Request request : (requests)) {
    if (!() && !()) {
      ();
    }
  }
  ();
}

All tasks that have not been completed and are in a stopped state will be executed. Requests is a running request cache collection, which is a set collection, and the request queue pendingRequests in a waiting state will be cleaned. This is what Glide senses the declaration cycle onStart.

RequestManager's onStop method

public synchronized void onStop() {
  pauseRequests();
  ();
}

RequestTracker's pauseRequests method

public void pauseRequests() {
  isPaused = true;
  for (Request request : (requests)) {
    if (()) {
      ();
      (request);
    }
  }
}

(): Pause all requests in the running cache collection and add these requests to the waiting cache request collection pendingRequests

RequestManager's onDestroy

public synchronized void onDestroy() {
  ();
  for (Target<?> target : ()) {
    clear(target);
  }
  ();
  ();
  (this);
  (connectivityMonitor);
  (addSelfToLifecycle);
  (this);
}

You can see that Glide senses that onDestroy will perform various resource releases and listening removal operations.

load operation

RequestManager load method

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

The return value of the load method is RequestBuilder, which is created through new

RequestBuilder construction method

protected RequestBuilder(
    @NonNull Glide glide,
    RequestManager requestManager,
    Class<TranscodeType> transcodeClass,
    Context context) {
   = glide;
   = requestManager;
   = transcodeClass;
   = context;
   = (transcodeClass);
   = ();
  initRequestListeners(());
  apply(());
}

initRequestListeners: Initialize listening

apply:Calibrate parameters

The load process is relatively simple, the key into will be very complicated

into process analysis

public <Y extends Target<TranscodeType>> Y into(@NonNull Y target) {
  return into(target, /*targetListener=*/ null, ());
}
<Y extends Target<TranscodeType>> Y into(
    @NonNull Y target,
    @Nullable RequestListener<TranscodeType> targetListener,
    Executor callbackExecutor) {
    return into(target, targetListener, /*options=*/ this, callbackExecutor);
 }

Another overload method into:

private <Y extends Target<TranscodeType>> Y into(
    @NonNull Y target,
    @Nullable RequestListener<TranscodeType> targetListener,
    BaseRequestOptions<?> options,
    Executor callbackExecutor) {
  (target);
  if (!isModelSet) {
    throw new IllegalArgumentException("You must call #load() before calling #into()");
  }
  Request request = buildRequest(target, targetListener, options, callbackExecutor);
  Request previous = ();
  if ((previous)
      && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
    if (!(previous).isRunning()) {
      ();
    }
    return target;
  }
  (target);
  (request);
  (target, request);
  return target;
}

The buildRequest will be passed and a SingleRequest is created. This must be understood in advance and needs to be used in the subsequent process.

(target, request): This is the main process. Let's take a look at the track method of RequestManager.

RequestManager track method

synchronized void track(@NonNull Target<?> target, @NonNull Request request) {
  (target);
  (request);
}

RequestTracker's runRequest method

public void runRequest(@NonNull Request request) {
  (request);
  if (!isPaused) {
    ();
  } else {
    ();
    if ((TAG, )) {
      (TAG, "Paused, delaying request");
    }
    (request);
  }
}

The Request here is an interface, and the begin method is called. This Request is the SingleRequest created earlier.

SingleRequest begin

public void begin() {
  synchronized (requestLock) {
    ...
    if (status == ) {
      onResourceReady(
          resource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
      return;
    }
    cookie = (TAG);
    status = Status.WAITING_FOR_SIZE;
    if ((overrideWidth, overrideHeight)) {
      onSizeReady(overrideWidth, overrideHeight);
    } else {
      (this);
    }
    if ((status ==  || status == Status.WAITING_FOR_SIZE)
        && canNotifyStatusChanged()) {
      (getPlaceholderDrawable());
    }
    if (IS_VERBOSE_LOGGABLE) {
      logV("finished run method in " + (startTime));
    }
  }
}

If the width and height are not set, the call (this) will be called to get the size, and if the onSizeReady method is set, the onSizeReady method will be executed

SingleRequest's onSizeReady method

public void onSizeReady(int width, int height) {
  ();
  synchronized (requestLock) {
    if (IS_VERBOSE_LOGGABLE) {
      logV("Got onSizeReady in " + (startTime));
    }
    if (status != Status.WAITING_FOR_SIZE) {
      return;
    }
    status = ;
    float sizeMultiplier = ();
     = maybeApplySizeMultiplier(width, sizeMultiplier);
     = maybeApplySizeMultiplier(height, sizeMultiplier);

    if (IS_VERBOSE_LOGGABLE) {
      logV("finished setup for calling load in " + (startTime));
    }
    loadStatus =
        (
            glideContext,
            model,
            (),
            ,
            ,
            (),
            transcodeClass,
            priority,
            (),
            (),
            (),
            (),
            (),
            (),
            (),
            (),
            (),
            this,
            callbackExecutor);
    if (status != ) {
      loadStatus = null;
    }
    if (IS_VERBOSE_LOGGABLE) {
      logV("finished onSizeReady in " + (startTime));
    }
  }
}

Next, call the load method of Engin

How to load the Engin

public <R> LoadStatus load(
    GlideContext glideContext,
    Object model,
    Key signature,
    int width,
    int height,
    Class<?> resourceClass,
    Class<R> transcodeClass,
    Priority priority,
    DiskCacheStrategy diskCacheStrategy,
    Map<Class<?>, Transformation<?>> transformations,
    boolean isTransformationRequired,
    boolean isScaleOnlyOrNoTransform,
    Options options,
    boolean isMemoryCacheable,
    boolean useUnlimitedSourceExecutorPool,
    boolean useAnimationPool,
    boolean onlyRetrieveFromCache,
    ResourceCallback cb,
    Executor callbackExecutor) {
  long startTime = VERBOSE_IS_LOGGABLE ? () : 0;
  EngineKey key =
      (
          model,
          signature,
          width,
          height,
          transformations,
          resourceClass,
          transcodeClass,
          options);
  EngineResource<?> memoryResource;
  synchronized (this) {
    memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
    if (memoryResource == null) {
      return waitForExistingOrStartNewJob(
          glideContext,
          model,
          signature,
          width,
          height,
          resourceClass,
          transcodeClass,
          priority,
          diskCacheStrategy,
          transformations,
          isTransformationRequired,
          isScaleOnlyOrNoTransform,
          options,
          isMemoryCacheable,
          useUnlimitedSourceExecutorPool,
          useAnimationPool,
          onlyRetrieveFromCache,
          cb,
          callbackExecutor,
          key,
          startTime);
    }
  }
  // Avoid calling back while holding the engine lock, doing so makes it easier for callers to
  // deadlock.
  (
      memoryResource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
  return null;
}

The loadFromMemory method is to search from the cache first. If there is any, you can use it directly.

loadFromMemory method

@Nullable
private EngineResource<?> loadFromMemory(
    EngineKey key, boolean isMemoryCacheable, long startTime) {
  if (!isMemoryCacheable) {
    return null;
  }
  EngineResource<?> active = loadFromActiveResources(key);
  if (active != null) {
    if (VERBOSE_IS_LOGGABLE) {
      logWithTimeAndKey("Loaded resource from active resources", startTime, key);
    }
    return active;
  }
  EngineResource<?> cached = loadFromCache(key);
  if (cached != null) {
    if (VERBOSE_IS_LOGGABLE) {
      logWithTimeAndKey("Loaded resource from cache", startTime, key);
    }
    return cached;
  }
  return null;
}

loadFromActiveResources: Look up from the active cache. If there is a direct return to use, this cache is a Map collection.

loadFromCache: Look up from memory cache, if used directly, this is an LRU collection

The above two are all memory caches, and as long as they are displayed on the page, they will be cached in the active cache.

Engin's waitForExistingOrStartNewJob method

private &lt;R&gt; LoadStatus waitForExistingOrStartNewJob(
    GlideContext glideContext,
    Object model,
    Key signature,
    int width,
    int height,
    Class&lt;?&gt; resourceClass,
    Class&lt;R&gt; transcodeClass,
    Priority priority,
    DiskCacheStrategy diskCacheStrategy,
    Map&lt;Class&lt;?&gt;, Transformation&lt;?&gt;&gt; transformations,
    boolean isTransformationRequired,
    boolean isScaleOnlyOrNoTransform,
    Options options,
    boolean isMemoryCacheable,
    boolean useUnlimitedSourceExecutorPool,
    boolean useAnimationPool,
    boolean onlyRetrieveFromCache,
    ResourceCallback cb,
    Executor callbackExecutor,
    EngineKey key,
    long startTime) {
  EngineJob&lt;?&gt; current = (key, onlyRetrieveFromCache);
  if (current != null) {
    (cb, callbackExecutor);
    if (VERBOSE_IS_LOGGABLE) {
      logWithTimeAndKey("Added to existing load", startTime, key);
    }
    return new LoadStatus(cb, current);
  }
  EngineJob&lt;R&gt; engineJob =  //This is the thread pool manager. There are various management in it.      (
          key,
          isMemoryCacheable,
          useUnlimitedSourceExecutorPool,
          useAnimationPool,
          onlyRetrieveFromCache);
  DecodeJob&lt;R&gt; decodeJob =  //This is a specific task      (
          glideContext,
          model,
          key,
          signature,
          width,
          height,
          resourceClass,
          transcodeClass,
          priority,
          diskCacheStrategy,
          transformations,
          isTransformationRequired,
          isScaleOnlyOrNoTransform,
          onlyRetrieveFromCache,
          options,
          engineJob);
  (key, engineJob);
  (cb, callbackExecutor);
  (decodeJob);
  if (VERBOSE_IS_LOGGABLE) {
    logWithTimeAndKey("Started new load", startTime, key);
  }
  return new LoadStatus(cb, engineJob);
}

engineJob manages a thread pool, decodeJob is the abstraction of the task and is a Runable, so the following will execute the run method of the task.

DecodeJob run method

public void run() {
  ...
  try {
    if (isCancelled) {
      notifyFailed();
      return;
    }
    runWrapped();
  } catch (CallbackException e) {
    // If a callback not controlled by Glide throws an exception, we should avoid the Glide
    // specific debug logic below.
    throw e;
  } catch (Throwable t) {
    ..
  } finally {
    // Keeping track of the fetcher here and calling cleanup is excessively paranoid, we call
    // close in all cases anyway.
    if (localFetcher != null) {
      ();
    }
    ();
  }
}

DecodeJob's runWrapped method

private void runWrapped() {
  switch (runReason) {
    case INITIALIZE:
      stage = getNextStage();
      currentGenerator = getNextGenerator();
      runGenerators();
      break;
    case SWITCH_TO_SOURCE_SERVICE:
      runGenerators();
      break;
    case DECODE_DATA:
      decodeFromRetrievedData();
      break;
    default:
      throw new IllegalStateException("Unrecognized run reason: " + runReason);
  }
}

By default, the runGenerators() of case INITIALIZE will be executed. The getNextGenerator gets SourceGenerator. This must be clear, otherwise you will not be able to continue.

SourceGenerator's startNext method

public boolean startNext() {
  ...
  if (sourceCacheGenerator != null && ()) {
    return true;
  }
  sourceCacheGenerator = null;
  loadData = null;
  boolean started = false;
  while (!started && hasNextModelLoader()) {
    loadData = ().get(loadDataListIndex++);
    if (loadData != null
        && (().isDataCacheable(())
            || (()))) {
      started = true;
      startNextLoad(loadData);
    }
  }
  return started;
}

DecodeHelper's getLoadData method

List<LoadData<?>> getLoadData() {
  if (!isLoadDataSet) {
    isLoadDataSet = true;
    ();
    List<ModelLoader<Object, ?>> modelLoaders = ().getModelLoaders(model);
    //noinspection ForLoopReplaceableByForEach to improve perf
    for (int i = 0, size = (); i < size; i++) {
      ModelLoader<Object, ?> modelLoader = (i);
      LoadData<?> current = (model, width, height, options);
      if (current != null) {
        (current);
      }
    }
  }
  return loadData;
}

HttpGlideUrlLoader buildLoadData method

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 = (model, 0, 0);
    if (url == null) {
      (model, 0, 0, model);
      url = model;
    }
  }
  int timeout = (TIMEOUT);
  return new LoadData<>(url, new HttpUrlFetcher(url, timeout));
}

You can see that the final return is loadData containing HttpUrlFetcher

SourceGenerator's startNextLoad method

private void startNextLoad(final LoadData<?> toStart) {
  (
      (),
      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);
          }
        }
      });
}

The fetcher is HttpUrlFetcher

HttpUrlFetcher loadData method

public void loadData(
    @NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
  long startTime = ();
  try {
    InputStream result = loadDataWithRedirects((), 0, null, ());
    (result);
  } catch (IOException e) {
    if ((TAG, )) {
      (TAG, "Failed to load data for url", e);
    }
    (e);
  } finally {
    if ((TAG, )) {
      (TAG, "Finished http url fetcher fetch in " + (startTime));
    }
  }
}

loadDataWithRedirects method

private InputStream loadDataWithRedirects(
    URL url, int redirects, URL lastUrl, Map<String, String> headers) throws HttpException {
  if (redirects >= MAXIMUM_REDIRECTS) {
    throw new HttpException(
        "Too many (> " + MAXIMUM_REDIRECTS + ") redirects!", INVALID_STATUS_CODE);
  } else {
    // Comparing the URLs using .equals performs additional network I/O and is generally broken.
    // See /2006/11/.
    try {
      if (lastUrl != null && ().equals(())) {
        throw new HttpException("In re-direct loop", INVALID_STATUS_CODE);
      }
    } catch (URISyntaxException e) {
      // Do nothing, this is best effort.
    }
  }
  urlConnection = buildAndConfigureConnection(url, headers);
  try {
    // Connect explicitly to avoid errors in decoders if connection fails.
    ();
    // Set the stream so that it's closed in cleanup to avoid resource leaks. See #2352.
    stream = ();
  } catch (IOException e) {
    throw new HttpException(
        "Failed to connect or obtain data", getHttpStatusCodeOrInvalid(urlConnection), e);
  }
  ...
}

Here I see the InputStream where the urlConnection gets the picture. Finally, it requests from a network link to the stream, and then converts the stream into a bitmap display process. Those who are interested can continue to view it.

Declare cycle and cache summary Glide calls the with function. If the incoming ApplicationContext or the child thread will not create a blank fragment for us, it will not monitor the life cycle, and the resource will not be automatically released, so there is no need to pass the ApplicationContext. In other cases, glide will automatically create a blank Fragment for life cycle monitoring and automatically release resources.

Glide's cache is divided into 4 levels of cache: active cache, memory cache, disk cache, and network cache

  • The active cache is a collection of maps. The image will be first searched from this cache.
  • Memory cache is a collection of LRU caches. If the cache is not available, it will be searched from the memory cache.
  • Disk cache is a disklrucache cache collection. If it is not found from the memory cache, it will be searched from the disk cache.
  • Network cache. If the disk cache does not exist, you need to request to load from the network.

This is the end of this article about the in-depth analysis of the source code layer of the Android image loading framework Gilde. For more related Android Gilde content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!