In this paper, starting from vivo Internet technology WeChat public links: mp.weixin.qq.com/s/cPLkefpEb… Author: Lian Lingneng

There are many image loading solutions on Android, but the official one is Glide. Glide provides a simple and easy to use API, the whole framework is also easy to expand, such as can replace the network request library, but also provides a complete cache mechanism, the application layer does not need to manage their own image cache and access, the framework will be divided into memory cache, file cache and remote cache. Rather than start with simple usage, this article will focus on the analysis of caching mechanisms.

Were reviewed,

Before you start, think about Glide cache:

  • How many levels of cache does Glide have?

  • What is the relationship between Glide memory caches?

  • Glide local file IO and network request are one thread? If not, how to implement thread switch?

  • Glide network request after the data directly returned to the user or saved and returned?

The load start entry starts with engine.load (), and let’s look at the comments for this method,

  • Resources that are not referenced by Active Resources are placed in the Memory Cache. If not, Resources that are not referenced by Active Resources are moved down.

  • Check if there are any required resources in the Memory Cache and return if there are. If there are no resources in the Memory Cache, continue down.

  • If the current job has the same key, the callback will be added to the current job. If the key is not the same, a new job will be created to start the new job.

* Starts a load for the given arguments.
*
* <p>Must be called on the main thread.
*
* <p>The flow for any request is as follows:
* <ul>
*   <li>Check the current set of actively used resources, return the active resource if
*   present, and move any newly inactive resources into the memory cache.</li>
*   <li>Check the memory cache and provide the cached resource if present.</li>
*   <li>Check the current set of in progress loads and add the cb to the in progress load if
*   one is present.</li>
*   <li>Start a new load.</li>
* </ul>
Copy the code


ok, find the source code.

Second, memory cache

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) { Util.assertMainThread(); long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0; EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations, resourceClass, transcodeClass, options); // focus 1 EngineResource<? > active = loadFromActiveResources(key, isMemoryCacheable);if(active ! = null) { cb.onResourceReady(active, DataSource.MEMORY_CACHE);if (VERBOSE_IS_LOGGABLE) {
      logWithTimeAndKey("Loaded resource from active resources", startTime, key);
    }
    returnnull; } // focus 2 EngineResource<? > cached = loadFromCache(key, isMemoryCacheable);if(cached ! = null) { cb.onResourceReady(cached, DataSource.MEMORY_CACHE);if (VERBOSE_IS_LOGGABLE) {
      logWithTimeAndKey("Loaded resource from cache", startTime, key);
    }
    returnnull; } // focus 3 EngineJob<? > current = jobs.get(key, onlyRetrieveFromCache);if(current ! = null) { current.addCallback(cb);if (VERBOSE_IS_LOGGABLE) {
      logWithTimeAndKey("Added to existing load", startTime, key);
    }
    return new LoadStatus(cb, current);
  }
 
  EngineJob<R> engineJob =
      engineJobFactory.build(
          key,
          isMemoryCacheable,
          useUnlimitedSourceExecutorPool,
          useAnimationPool,
          onlyRetrieveFromCache);
 
  DecodeJob<R> decodeJob =
      decodeJobFactory.build(
          glideContext,
          model,
          key,
          signature,
          width,
          height,
          resourceClass,
          transcodeClass,
          priority,
          diskCacheStrategy,
          transformations,
          isTransformationRequired,
          isScaleOnlyOrNoTransform,
          onlyRetrieveFromCache,
          options,
          engineJob);
 
  jobs.put(key, engineJob);
 
  engineJob.addCallback(cb);
  // focus 4
  engineJob.start(decodeJob);
 
  if (VERBOSE_IS_LOGGABLE) {
    logWithTimeAndKey("Started new load", startTime, key);
  }
  return new LoadStatus(cb, engineJob);
}
Copy the code


Focus 1: This step loads resources from ActiveResources. Check whether memory cache is used, and return null if not. Otherwise, fetch data from ActiveResources:

// Engine.java @Nullable private EngineResource<? > loadFromActiveResources(Key key, boolean isMemoryCacheable) {if(! isMemoryCacheable) {returnnull; } EngineResource<? > active = activeResources.get(key);if(active ! = null) { active.acquire(); }return active;
 }
Copy the code


Next, take a look at ActiveResources, which uses weak references to save used resources.

final class ActiveResources {
 
  ...
  private final Handler mainHandler = new Handler(Looper.getMainLooper(), new Callback() {
    @Override
    public boolean handleMessage(Message msg) {
      if (msg.what == MSG_CLEAN_REF) {
        cleanupActiveReference((ResourceWeakReference) msg.obj);
        return true;
      }
      return false; }}); @VisibleForTesting final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>(); . }Copy the code


The type of callback after a successful fetch is also memory cache:

EngineResource<? > cached = loadFromCache(key, isMemoryCacheable);if(cached ! = null) { cb.onResourceReady(cached, DataSource.MEMORY_CACHE);return null;
}
Copy the code


EngineResource = EngineResource = EngineResource = EngineResource = EngineResource = EngineResource = EngineResource The resource is then moved to ActiveResources, the cache mentioned above:

// Engine.java private final MemoryCache cache; private EngineResource<? > loadFromCache(Key key, boolean isMemoryCacheable) {if(! isMemoryCacheable) {returnnull; } EngineResource<? > cached = getEngineResourceFromCache(key);if(cached ! = null) { cached.acquire(); activeResources.activate(key, cached); }returncached; } private EngineResource<? > getEngineResourceFromCache(Key key) { Resource<? > cached = cache.remove(key); final EngineResource<? > 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, true /*isMemoryCacheable*/, true /*isRecyclable*/); } return result; }Copy the code


Cache is an implementation of the MemoryCache interface. If this is not set, LruResourceCache is the default LRU cache:

// GlideBuilder.java
if (memoryCache == null) {
   memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
}
Copy the code


Take a look at EngineResource, which adds reference counting to resources:

// EngineResource.java private final boolean isCacheable; private final boolean isRecyclable; private ResourceListener listener; private Key key; private int acquired; private boolean isRecycled; private final Resource<Z> resource; interface ResourceListener { void onResourceReleased(Key key, EngineResource<? > resource); } EngineResource(Resource<Z> toWrap, boolean isCacheable, boolean isRecyclable) { resource = Preconditions.checkNotNull(toWrap); this.isCacheable = isCacheable; this.isRecyclable = isRecyclable; } voidsetResourceListener(Key key, ResourceListener listener) {
    this.key = key;
    this.listener = listener;
  }
 
  Resource<Z> getResource() {
    return resource;
  }
 
  boolean isCacheable() {
    return isCacheable;
  }
 
  @NonNull
  @Override
  public Class<Z> getResourceClass() {
    return resource.getResourceClass();
  }
 
  @NonNull
  @Override
  public Z get() {
    return resource.get();
  }
 
  @Override
  public int getSize() {
    return resource.getSize();
  }
 
  @Override
  public void recycle() {
    if (acquired > 0) {
      throw new IllegalStateException("Cannot recycle a resource while it is still acquired");
    }
    if (isRecycled) {
      throw new IllegalStateException("Cannot recycle a resource that has already been recycled");
    }
    isRecycled = true;
    if (isRecyclable) {
      resource.recycle();
    }
  }
 
  void acquire() {
    if (isRecycled) {
      throw new IllegalStateException("Cannot acquire a recycled resource");
    }
    if(! Looper.getMainLooper().equals(Looper.myLooper())) { throw new IllegalThreadStateException("Must call acquire on the main thread");
    }
    ++acquired;
  }
 
  void release() {
    if (acquired <= 0) {
      throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
    }
    if(! Looper.getMainLooper().equals(Looper.myLooper())) { throw new IllegalThreadStateException("Must call release on the main thread");
    }
    if(--acquired == 0) { listener.onResourceReleased(key, this); }}Copy the code


After release the call will determine whether the reference count is 0. If it does, the onResourceReleased, in this case Engine, will remove the resource from the ActiveResources. The resource is cacheable by default, so it will put the resource into the LruCache.

// Engine.java @Override public void onResourceReleased(Key cacheKey, EngineResource<? > resource) { Util.assertMainThread(); activeResources.deactivate(cacheKey);if (resource.isCacheable()) {
      cache.put(cacheKey, resource);
    } else{ resourceRecycler.recycle(resource); } } // ActiveResources.java void activate(Key key, EngineResource<? > resource) { ResourceWeakReference toPut = new ResourceWeakReference( key, resource, getReferenceQueue(), isActiveResourceRetentionAllowed); ResourceWeakReference removed = activeEngineResources.put(key, toPut);if(removed ! = null) { removed.reset(); } } void deactivate(Key key) { ResourceWeakReference removed = activeEngineResources.remove(key);if (removed != null) {
      removed.reset();
    }
  }
Copy the code


If the reference count is zero and the Resource has not been recycled, the actual Resource.recycle() is called and one of the bitmapresources is recycled into the Bitmap pool. The LRU Cache is also used. This is not relevant to today’s topic, so we will not continue to expand.

// BitmapResource.java
 @Override
 public void recycle() {
   bitmapPool.put(bitmap);
 }
Copy the code


If the job is already running, add a callback and return LoadStatus. This allows the user to cancel the job:

// Engine.java EngineJob<? > current = jobs.get(key, onlyRetrieveFromCache);if(current ! = null) { current.addCallback(cb);if (VERBOSE_IS_LOGGABLE) {
      logWithTimeAndKey("Added to existing load", startTime, key);
    }
   returnnew LoadStatus(cb, current); } // LoadStatus public static class LoadStatus { private final EngineJob<? > engineJob; private final ResourceCallback cb; LoadStatus(ResourceCallback cb, EngineJob<? > engineJob) { this.cb = cb; this.engineJob = engineJob; } public voidcancel() { engineJob.removeCallback(cb); }}Copy the code


Moving on to Focus 4, you need to create background tasks to pull disk files or initiate network requests.

Disk cache

// Engine.java
   EngineJob<R> engineJob =
        engineJobFactory.build(
            key,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache);
 
    DecodeJob<R> decodeJob =
        decodeJobFactory.build(
            glideContext,
            model,
            key,
            signature,
            width,
            height,
            resourceClass,
            transcodeClass,
            priority,
            diskCacheStrategy,
            transformations,
            isTransformationRequired,
            isScaleOnlyOrNoTransform,
            onlyRetrieveFromCache,
            options,
            engineJob);
 
    jobs.put(key, engineJob);
 
    engineJob.addCallback(cb);
    engineJob.start(decodeJob);
    return new LoadStatus(cb, engineJob);
Copy the code


The DecodeJob can be divided into the following stages according to the source of resources to be decoded:

// DecodeJob.java
  /**
   * 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


When constructing a DecodeJob, the state is set to INITIALIZE.

After the two jobs are constructed, engineJob. start(DecodeJob) is called. The first call is getNextStage to determine the next stage, which is related to the disk caching strategy passed in to DiskCacheStrategy.

The disk policies are as follows:

  • **ALL: ** Caches raw and converted data

  • **NONE: ** No cache

  • **DATA: ** Raw DATA, not decoded or converted

  • **RESOURCE: ** Caches decoded data

  • **AUTOMATIC(default) : ** Automatically selects the appropriate cache side based on conditions such as’ EncodeStrategy ‘and’ DataSource ‘

The default AUTOMATIC mode allows the decoding of cached resources:

public static final DiskCacheStrategy AUTOMATIC = new DiskCacheStrategy() {
    @Override
    public boolean isDataCacheable(DataSource dataSource) {
      return dataSource == DataSource.REMOTE;
    }
 
    @Override
    public boolean isResourceCacheable(boolean isFromAlternateCacheKey, DataSource dataSource,
        EncodeStrategy encodeStrategy) {
      return ((isFromAlternateCacheKey && dataSource == DataSource.DATA_DISK_CACHE)
          || dataSource == DataSource.LOCAL)
          && encodeStrategy == EncodeStrategy.TRANSFORMED;
    }
 
    @Override
    public boolean decodeCachedResource() {
      return true;
    }
 
    @Override
    public boolean decodeCachedData() {
      return true; }};Copy the code


So getNextStage returns stage.resource_cache, and then diskCacheExecutor is returned in start and DecodeJob is executed:

// EngineJob.java
public void start(DecodeJob<R> decodeJob) {
    this.decodeJob = decodeJob;
    GlideExecutor executor = decodeJob.willDecodeFromCache()
        ? diskCacheExecutor
        : getActiveSourceExecutor();
    executor.execute(decodeJob);
}
 
// DecodeJob.java
  boolean willDecodeFromCache() {
    Stage firstStage = getNextStage(Stage.INITIALIZE);
    return firstStage == Stage.RESOURCE_CACHE || firstStage == Stage.DATA_CACHE;
  }
 
  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:
        // Skip loading from source if the user opted to only retrieve the resource from cache.
        return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
      case SOURCE:
      case FINISHED:
        return Stage.FINISHED;
      default:
        throw new IllegalArgumentException("Unrecognized stage: "+ current); }}Copy the code


DecodeJob calls runWrapped to run() and runReason to INITIALIZE. Following the analysis instructions above, we get a ResourceCacheGenerator and call runGenerators:

// DecodeJob.java 
private void runWrapped() {
    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);
    }
  }
 
  private DataFetcherGenerator getNextGenerator() {
    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


For runGenerators, startNext is called, and currentGenerator is ResourceCacheGenerator, so its startNext method is called:

// DecodeJob.java 
private void runGenerators() {
    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


See ResourceCacheGenerator. StartNext (), which is the key logic, First get the ModelLoader that supports the resource type from Registry (where ModelLoader is passed in when Glide is constructed), Then LoadData from ModelLoader structure, then can get the DataFetcher, (about ModelLoader/LoadData/DataFetcher is beyond the scope of this, the relationship between behind have a chance to write another) through its LoadData method to load data:

@Override
 public boolean startNext() {
   List<Key> sourceIds = helper.getCacheKeys();
   if (sourceIds.isEmpty()) {
     return false; } List<Class<? >> resourceClasses = helper.getRegisteredResourceClasses();if (resourceClasses.isEmpty()) {
     if (File.class.equals(helper.getTranscodeClass())) {
       return false; }}while(modelLoaders == null || ! hasNextModelLoader()) { resourceClassIndex++;if (resourceClassIndex >= resourceClasses.size()) {
       sourceIdIndex++;
       if (sourceIdIndex >= sourceIds.size()) {
         return false;
       }
       resourceClassIndex = 0;
     }
 
     Key sourceId = sourceIds.get(sourceIdIndex); Class<? > resourceClass = resourceClasses.get(resourceClassIndex); Transformation<? > transformation = helper.getTransformation(resourceClass); currentKey = new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops helper.getArrayPool(),sourceId,
             helper.getSignature(),
             helper.getWidth(),
             helper.getHeight(),
             transformation,
             resourceClass,
             helper.getOptions());
     cacheFile = helper.getDiskCache().get(currentKey);
     if(cacheFile ! = null) {sourceKey = sourceId;
       modelLoaders = helper.getModelLoaders(cacheFile);
       modelLoaderIndex = 0;
     }
   }
 
   loadData = null;
   boolean started = false;
   while(! started && hasNextModelLoader()) { ModelLoader<File, ? > modelLoader = modelLoaders.get(modelLoaderIndex++); loadData = modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(), helper.getOptions());if(loadData ! = null && helper.hasLoadPath(loadData.fetcher.getDataClass())) { started =true; loadData.fetcher.loadData(helper.getPriority(), this); }}return started;
 }
Copy the code


If the desired Resource is not found in the Resource, startNext returns false and goes into the loop body when in runGenerators:

  • GetNextStage is then repeated. Since Stage is now RESOURCE_CACHE, DataCacheGenerator is returned. The logic is the same as ResourceCacheGenerator, If you still don’t find what you need, enter the circulation.

  • GetNextStage will get resources only from disk depending on whether it is used. If so, it will notify onLoadFailed. If not, set the current Stage to stage.source and proceed.

  • The state goes into the if conditional logic inside the loop, which calls the reschedule.

  • In reschedule, set runReason to SWITCH_TO_SOURCE_SERVICE and callback via callback.

  • Callback in DecodeJob is passed by EngineJob, so now return to EngineJob.

  • Switch to the network thread pool with The getActiveSourceExecutor in EngineJob, execute the DecodeJob and it’s ready to initiate the network request.

Network cache

In the stage. SOURCE phase, the SourceGenerator is returned by getNextGenerator, so the current currentGenerator is that.

The process remains the same. The SourceGenerator calls the startNext method to retrieve the corresponding DataFetcher (HttpUrlFetcher in this case) and initiates the network request.

// DecodeJob.java 
private void runGenerators() {...while(! isCancelled && currentGenerator ! = null && ! (isStarted = currentGenerator.startNext())) { stage = getNextStage(stage); currentGenerator = getNextGenerator();if (stage == Stage.SOURCE) {
        reschedule();
        return; }}... } @Override public voidreschedule() { runReason = RunReason.SWITCH_TO_SOURCE_SERVICE; callback.reschedule(this); } // EngineJob.java @Override public void reschedule(DecodeJob<? > job) { getActiveSourceExecutor().execute(job); }Copy the code


First slow down, in fact, this article to the above can end, Glide involved in the five level cache has been involved, is really can end? No, does the network request return have anything to do with caching? HttpUrlFetcher, onDataReady, callback (SourceGenerator)

// HttpUrlFetcher.java
@Override
  public void loadData(@NonNull Priority priority,
      @NonNull DataCallback<? super InputStream> callback) {
    long startTime = LogTime.getLogTime();
    try {
      InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
      callback.onDataReady(result);
    } catch (IOException e) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Failed to load data for url", e);
      }
      callback.onLoadFailed(e);
    } finally {
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Finished http url fetcher fetch in "+ LogTime.getElapsedMillis(startTime)); } } } // EngineJob.java @Override public void reschedule(DecodeJob<? > job) { getActiveSourceExecutor().execute(job); }Copy the code


It normally goes into the if logic, assigns dataToCache, and then calls cb.reschedule. Cb is passed in when the SourceGenerator is constructed. Cb is the DecodeJob.

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


The DecodeJob calls back to the EngineJob in the reschedule and returns to the startNext() logic in the SourceGenerator.

// DecodeJob.java
  private DataFetcherGenerator getNextGenerator() {
    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);
    }
  }
 
  @Override
  public void reschedule() {
    runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
    callback.reschedule(this);
  }
Copy the code


Now dataToCache! = null, enter the first if logic.

Call cacheData within the logic, which is obvious, to keep the data locally and then construct a DataCacheGenerator.

The DataCacheGenerator, as previously analyzed, is used to load local raw data. This will load successfully, returning true.

// SourceGenerator.java
@Override
  public boolean startNext() {
    if(dataToCache ! = null) { Object data = dataToCache; dataToCache = null; cacheData(data); }if (sourceCacheGenerator ! = null &&sourceCacheGenerator.startNext()) {
      return true; }... } private void cacheData(Object dataToCache) { long startTime = LogTime.getLogTime(); try { Encoder<Object> encoder = helper.getSourceEncoder(dataToCache); DataCacheWriter<Object> writer = new DataCacheWriter<>(encoder, dataToCache, helper.getOptions()); originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature()); helper.getDiskCache().put(originalKey, writer); } finally { loadData.fetcher.cleanup(); }sourceCacheGenerator =
        new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
  }
Copy the code


A series of callbacks followed:

The startNext logic of the DataCacheGenerator passes itself to the DataFetcher as a callback, calling onDataReady when the local data is successfully loaded.

// DataCacheGenerator
  @Override
  public boolean startNext() {... loadData = null; boolean started =false;
    while(! started && hasNextModelLoader()) { ...if(loadData ! = null && helper.hasLoadPath(loadData.fetcher.getDataClass())) { started =true; loadData.fetcher.loadData(helper.getPriority(), this); }}return started;
  }
 
  @Override
  public void onDataReady(Object data) {
    cb.onDataFetcherReady(sourceKey, data, loadData.fetcher, DataSource.DATA_DISK_CACHE, sourceKey);
  }
Copy the code


The CB is now passed by the SourceGenerator, which in turn calls back its own CB, which is passed by the DecodeJob when it is constructed.

// SourceGenerator.java
  @Override
  public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<? > fetcher, DataSource dataSource, Key attemptedKey) { cb.onDataFetcherReady(sourceKey, data, fetcher, loadData.fetcher.getDataSource(), sourceKey);
  }
 
// DecodeJob.java
  @Override
  public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<? > fetcher, DataSource dataSource, Key attemptedKey) { this.currentSourceKey =sourceKey;
    this.currentData = data;
    this.currentFetcher = fetcher;
    this.currentDataSource = dataSource;
    this.currentAttemptingKey = attemptedKey;
    if(Thread.currentThread() ! = currentThread) { runReason = RunReason.DECODE_DATA; callback.reschedule(this); }else{ try { decodeFromRetrievedData(); } finally { GlideTrace.endSection(); }}}Copy the code


The SourceGenerator switches the DecodeJob to the ActiveSourceExecutor thread above. Remember where the DecodeJob was started in the first place? Start the EngineJob and then execute the DecodeJob in diskCacheExecutor.

// EngineJob.java
  public void start(DecodeJob<R> decodeJob) {
    this.decodeJob = decodeJob;
    GlideExecutor executor = decodeJob.willDecodeFromCache()
        ? diskCacheExecutor
        : getActiveSourceExecutor();
    executor.execute(decodeJob);
  }

Copy the code


So the onDataFetcherReady in the DecodeJob will go to the first if logic, We then assign runReason = runreason.decode_data and call the engine.reschedule again to switch the worker thread to ActiveSourceExecutor.

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


Then we go to DecodeJob again, now we go to the DECODE_DATA branch, where we call ResourceDecoder to decode the data:

private void runWrapped() {
    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


Call notifyComplete(result, dataSource) after decoding success;

private void notifyComplete(Resource<R> resource, DataSource dataSource) {
  setNotifiedOrThrow();
  callback.onResourceReady(resource, dataSource);
}
Copy the code


Five, the summary

Now for the first few questions.

1. How many levels of cache are there? Level five, what are they?

  • Active Resources

  • Memory Cache

  • Resource Type (Resource Disk Cache)

  • Raw Data (Data Disk Cache)

  • Web caching

What is the relationship between Glide memory cache?

I drew a picture to show this relationship, in a nutshell.

Glide local file IO and network request are the same thread?

Obviously not. Local IO goes through diskCacheExecutor and network IO goes through ActiveSourceExecutor

4, Glide network request after the data directly returned to the user or first saved and then returned?

Instead of returning it directly to the user, a DataCacheGenerator is constructed in SourceGenerator to fetch the data.

For more content, please pay attention to vivo Internet technology wechat public account

Note: To reprint the article, please contact our wechat account: LABs2020