This is the 7th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.

When Glide finds that neither the active resource nor the memory cache has the desired data during data loading, it requests data from the file and the current network. We call this process the working process of the loading engine.

There are several key classes involved in the data loading process

Engine: The entry point to the loading Engine where data loading begins

EngineJob: Manages every load.

DecodeJob: Actual data loading location. The focus of this analysis.

After EngineJob calls start, the DecodeJob#run method is executed through the thread pool, while run calls runWrapped

public void run() { // ... Omit runWrapped(); / /... } 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

DecodeJob#getNextStage

RunReason is initially initialized to INITIALIZE in the constructor.

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

The purpose of getNextStage is to get the next state, If diskCacheStrategy is not configured, diskCacheStrategy #AUTOMATIC is used by default DiskCacheStrategy. DecodeCachedResource () and diskCacheStrategy decodeCachedData () returns true. The state flow is as follows.

DecodeJob#getNextGenerator

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

The state returned by getNextStage is RESOURCE_CACHE and currentGenerator is ResourceCacheGenerator.

DecodeJob#runGenerators

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(); } // Otherwise a generator started a new load and we expect to be called back in // onDataFetcherReady. }Copy the code

The runGenerators code logic is very simple, if currentGenerator #startNext returns true to end the current running process, Otherwise, get the next state and Generator until startNext() returns fasle or the current stage is stage.source.

Work process of ResourceCacheGenerator

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; } throw new IllegalStateException( "Failed to find any load path from " + helper.getModelClass() + " to " + helper.getTranscodeClass()); } 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); // PMD.AvoidInstantiatingObjectsInLoops Each iteration is comparatively expensive anyway, // we only run until the first one succeeds, the loop runs for only a limited // number of iterations on the order of 10-20 in the worst case. 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

The whole process is through

  1. DecodeHelper# getRegisteredResourceClasses access resources able to handle the request.
  2. According to the current resource resourceClass build cache key, according to the cache key to obtain the corresponding cache file (if any), in traverse all RegisteredResourceClasses still cannot obtain corresponding cache files, directly in return false.
  3. Read data from a cache file

The working process of DataCacheGenerator.

Let’s assume that the first time a resource is loaded, there is no cache. GetNextStage returns stage.data_cache. GetNextGenerator returns a DataCacheGenerator

public boolean startNext() { while (modelLoaders == null || ! hasNextModelLoader()) { sourceIdIndex++; if (sourceIdIndex >= cacheKeys.size()) { return false; } Key sourceId = cacheKeys.get(sourceIdIndex); // PMD.AvoidInstantiatingObjectsInLoops The loop iterates a limited number of times // and the actions it performs are much more expensive than a single allocation. @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") Key originalKey = new DataCacheKey(sourceId, helper.getSignature()); cacheFile = helper.getDiskCache().get(originalKey); if (cacheFile ! = null) { this.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

The working process of DataCacheGenerator is similar to that of ResourceCacheGenerator. A cache key is assembled and a file is retrieved based on the key. If the file exists, the data is retrieved.

The working process of SourceGenerator

GetNextStage returns stage. SOURCE when DataCacheGenerator#startNext returns false. GetNextGenerator returns SourceGenerator

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); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Finished encoding source to cache" + ", key: " + originalKey + ", data: " + dataToCache + ", encoder: " + encoder + ", duration: " + LogTime.getElapsedMillis(startTime)); } } finally { loadData.fetcher.cleanup(); } // After creating DataCacheGenerator to cache data, Reading data from the cache rather than remote data download sourceCacheGenerator = new DataCacheGenerator (Collections. SingletonList (loadData. SourceKey), helper, this); } public Boolean startNext() {// If (dataToCache! = null) { Object data = dataToCache; dataToCache = null; cacheData(data); } if (sourceCacheGenerator ! = null && sourceCacheGenerator.startNext()) { return true; } sourceCacheGenerator = null; loadData = null; boolean started = false; while (! started && hasNextModelLoader()) { loadData = helper.getLoadData().get(loadDataListIndex++); if (loadData ! = null && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource()) || helper.hasLoadPath(loadData.fetcher.getDataClass()))) { started = true; loadData.fetcher.loadData(helper.getPriority(), this); } } return started; }Copy the code

The SourceGenerator works as follows:

  1. Get data remotely
  2. After the data is loaded successfully, start startNext again to cache the data obtained remotely.
  3. Data is loaded through DataCacheGenerator.

DecodeJob Data callback

OnDataFetcherReady is called when the data is loaded successfully and it calls decodeFromRetrievedData

private void decodeFromRetrievedData() { if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Retrieved data", startFetchTime, "data: " + currentData + ", cache key: " + currentSourceKey + ", fetcher: " + currentFetcher); } Resource<R> resource = null; Resource = decodeFromData(currentFetcher, currentData, currentDataSource); } catch (GlideException e) { e.setLoggingDetails(currentAttemptingKey, currentDataSource); throwables.add(e); } if (resource ! = null) {// Save the decoded data, which can be used next time (in this case, save the data obtained from the file by ResourceCacheGenerator) and notify the data load is complete. notifyEncodeAndRelease(resource, currentDataSource); } else { runGenerators(); }}Copy the code