The entry method is enigine.load ()

  1. Loading from memory — loadFromMemory()
  2. Load from disk or network
public <R> LoadStatus load(/ * * * /) {
    long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;

    EngineKey key =
        keyFactory.buildKey(/ / * * *);EngineResource<? > memoryResource;synchronized (this) {
      memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);

      if (memoryResource == null) {
        return waitForExistingOrStartNewJob(
            / / * * *)
      }
    }
    cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE);
    return null;
}
Copy the code

Memory loading process:

  1. Load from a resource being used in memory
  2. Load from memory cache
privateEngineResource<? > loadFromMemory( EngineKey key,boolean isMemoryCacheable, long startTime) {
    if(! isMemoryCacheable) {return null; } EngineResource<? > active = loadFromActiveResources(key);if(active ! =null) {
      returnactive; } EngineResource<? > cached = loadFromCache(key);if(cached ! =null) {
      return cached;
    }

    return null;
  }
Copy the code

Load from a resource in use:

ActivityResources is a resource management class that saves the resources that are currently being used (holding the resources with WeakReference and saving them in a Map).

  privateEngineResource<? > loadFromActiveResources(Key key) { EngineResource<? > active = activeResources.get(key);if(active ! =null) {
      active.acquire();
    }

    return active;
  }
Copy the code

Loading from cache:

Get the cache from MemoryCache and, if any, add one to the resource reference count and add it to ActivityResources.

privateEngineResource<? > loadFromCache(Key key) { EngineResource<? > cached = getEngineResourceFromCache(key);if(cached ! =null) {
      cached.acquire();
      activeResources.activate(key, cached);
    }
    return cached;
  }

  privateEngineResource<? > getEngineResourceFromCache(Key key) { Resource<? > cached = cache.remove(key);finalEngineResource<? > result;if (cached == null) {
      result = null;
    } else if (cached instanceof EngineResource) {
      // Save an object allocation if we've cached an EngineResource (the typical case).result = (EngineResource<? >) cached; }else {
      result =
          new EngineResource<>(
              cached, /*isMemoryCacheable=*/ true./*isRecyclable=*/ true, key, /*listener=*/ this);
    }
    return result;
  }
Copy the code

If not in memory, need from the disk cache or the network load, the main logic in waitForExistingOrStartNewJob () :

  1. Check whether there is a loading task for the resource. If yes, add a callback to avoid repeated loading.
  2. Start a EngineJob callback that manages the loading, and the actual loading is done in the DecodeJob
private <R> LoadStatus waitForExistingOrStartNewJob(/ * * /) { EngineJob<? > current = jobs.get(key, onlyRetrieveFromCache);if(current ! =null) {
      current.addCallback(cb, callbackExecutor);
      return new LoadStatus(cb, current);
    }

    EngineJob<R> engineJob =
        engineJobFactory.build(/ * * /); DecodeJob
      
        decodeJob = decodeJobFactory.build(/**/
      );

    jobs.put(key, engineJob);
    engineJob.addCallback(cb, callbackExecutor);
    engineJob.start(decodeJob);
    return new LoadStatus(cb, engineJob);
  }
Copy the code

Take a look at the implementation of enginejob.start (), which puts DecodeJob into a thread pool for execution:

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

DecodeJob implements the Runnable interface, focusing on the run() method it implements:

  1. Check whether it has been canceled
  2. Call the runWrapped() method to perform the state transition
  3. Some exception handling and callback execution
public void run(a) {
 	/ / * * *DataFetcher<? > localFetcher = currentFetcher;try {
      if (isCancelled) {
        notifyFailed();
        return;
      }
      runWrapped();
    } catch (CallbackException e) {
      / / * * *
      throw e;
    } catch (Throwable t) {
      / / * * *
      if(stage ! = Stage.ENCODE) { throwables.add(t); notifyFailed(); }if(! isCancelled) {throw t;
      }
      throw t;
    } finally {
      / / * * *}}Copy the code

RunWrapped () is a key method of state transition that handles initialization — loading — decoding:

  1. Judge the current state
  2. Get the resource handler
  3. Running the resource handler
  private void runWrapped(a) {
    switch (runReason) {
      case INITIALIZE:
        stage = getNextStage(Stage.INITIALIZE);
        currentGenerator = getNextGenerator();
        runGenerators();
        break;
      case SWITCH_TO_SOURCE_SERVICE:
        runGenerators();
        break;
      case DECODE_DATA:
        decodeFromRetrievedData();
        break;
      default:
        throw new IllegalStateException("Unrecognized run reason: "+ runReason); }}Copy the code

The state transition relationship is in the getNextStage() method (which is related to disk caching policies) :

  1. RESOURCE_CACHE – Caches converted image resources
  2. DATA_CACHE – Only raw image resources are cached
  3. SOURCE — Not cached, only loaded from the network
  4. FINISHED — Only loaded from cache, state becomes FINISHED; Or the next state loaded from the network also ends
  private Stage getNextStage(Stage current) {
    switch (current) {
      case INITIALIZE:
        return diskCacheStrategy.decodeCachedResource()
            ? Stage.RESOURCE_CACHE
            : getNextStage(Stage.RESOURCE_CACHE);
      case RESOURCE_CACHE:
        return diskCacheStrategy.decodeCachedData()
            ? Stage.DATA_CACHE
            : getNextStage(Stage.DATA_CACHE);
      case DATA_CACHE:
        return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
      case SOURCE:
      case FINISHED:
        return Stage.FINISHED;
      default:
        throw new IllegalArgumentException("Unrecognized stage: "+ current); }}Copy the code

Get the data capture generator that should be used at this time by judging the current state:

  1. RESOURCE_CACHE corresponds to ResourceCacheGenerator(), which is used to get the cached transformed picture
  2. DATA_CACHE Corresponds to the original image resource that the DataCacheGenerator() user obtains from the cache
  3. SOURCE corresponds to SourceGenerator() for loading images from the network
  4. They both hold the FetcherReadyCallback interface implemented by DecodeJob, which handles the callback after the resource has loaded
  private DataFetcherGenerator getNextGenerator(a) {
    switch (stage) {
      case RESOURCE_CACHE:
        return new ResourceCacheGenerator(decodeHelper, this);
      case DATA_CACHE:
        return new DataCacheGenerator(decodeHelper, this);
      case SOURCE:
        return new SourceGenerator(decodeHelper, this);
      case FINISHED:
        return null;
      default:
        throw new IllegalStateException("Unrecognized stage: "+ stage); }}Copy the code

DecodeJob implements FetcherReadyCallback:

  @Override
  public void reschedule(a) {
    runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
    callback.reschedule(this);
  }
Copy the code

Take a look at the comment for RunReason:

private enum RunReason {
    /** The first time we've been submitted. */
    INITIALIZE,
    /** We want to switch from the disk cache service to the source executor. */
    SWITCH_TO_SOURCE_SERVICE,
    /** * We retrieved some data on a thread we don't own and want to switch back to our thread to * process the data. */
    DECODE_DATA,
  }
Copy the code

SWITCH_TO_SOURCE_SERVICE indicates conversion from disk caching to data processing.

DecodeJob DecodeJob DecodeJob DecodeJob DecodeJob DecodeJob DecodeJob

  @Override
  public void reschedule(DecodeJob
        job) {
    // Even if the job is cancelled here, it still needs to be scheduled so that it can clean itself
    // up.
    getActiveSourceExecutor().execute(job);
  }
Copy the code

DecodeJob’s run() method will continue to call runWrapped(), and since runReason has changed to SWITCH_TO_SOURCE, it will continue to call runGenerators().

  private void runGenerators(a) {
    currentThread = Thread.currentThread();
    startFetchTime = LogTime.getLogTime();
    boolean isStarted = false;
    while(! isCancelled && currentGenerator ! =null
        && !(isStarted = currentGenerator.startNext())) {
      stage = getNextStage(stage);
      currentGenerator = getNextGenerator();

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

(runGenerators() is a state machine that fetches image resources level by level. State transitions are implemented by getNextStage(), where the state is defined:

  /** Where we're trying to decode data from. */
  private enum Stage {
    /** The initial stage. */
    INITIALIZE,
    /** Decode from a cached resource. */
    RESOURCE_CACHE,
    /** Decode from cached source data. */
    DATA_CACHE,
    /** Decode from retrieved source. */
    SOURCE,
    /** Encoding transformed resources after a successful load. */
    ENCODE,
    /** No more viable stages. */
    FINISHED,
  }
Copy the code

So if the resource is found in the RESOURCE_CACHE and DATA_CACHE caches end, otherwise a network request is made.