Make writing a habit together! This is my first day to participate in the “Gold Digging Day New Plan · April More text challenge”, click to see the details of the activity.

Glide is a fast and efficient Android image loading library that focuses on smooth scrolling. Glide provides an easy-to-use API, a high-performance, scalable decode pipeline, and automated resource pooling technology.

From the main process analysis

Glide’s simplest use:

Glide
    .with(context)
    .load("https://github.com/bumptech/glide/blob/master/static/glide_logo.png")
    .into(iv)
Copy the code

Glide loading the picture is the easiest three steps:

  • Glide.with(context)In order to getGlideThe objects andRequestManagerObject and bindContextLife cycle;
  • RequestManager.load(url)To obtainRequestBuilderObject and bind the imageUrlIs not loaded yetUrlAddress;
  • RequestBuilder.into(iv)Load the image and display the captured image toImageViewIn the.

Let’s look at the implementation step by step in three steps.

Glide.with(context)

com.bumptech.glide.Glide#with(Context) public static RequestManager with(@NonNull Context context) { return getRetriever(context).get(context); } private static RequestManagerRetriever getRetriever(@Nullable Context context) { return Glide.get(context).getRequestManagerRetriever(); Public static Glide get(@nonNULL Context Context) {if (Glide == null) {GeneratedAppGlideModule annotationGeneratedModule = getAnnotationGeneratedGlideModules(context.getApplicationContext()); synchronized (Glide.class) { if (glide == null) { checkAndInitializeGlide(context, annotationGeneratedModule); } } } return glide; }Copy the code

Glide. () There are two big steps:

  • getRetriever(context)To obtainGlideAnd singleton objectsRequestManagerRetrieverObject;
  • get(context)To obtainRequestManagerObject is bound to the currentContextLife cycle of.

Glide singleton creation

Get (context)->checkAndInitializeGlide()->initializeGlide()->GlideBuilder#build(), Finally, Glide objects are created through the Builder pattern.

com.bumptech.glide.GlideBuilder#build

Glide build(@NonNull Context context) {
  if (sourceExecutor == null) {
    sourceExecutor = GlideExecutor.newSourceExecutor();
  }

  if (diskCacheExecutor == null) {
    diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();
  }

  if (animationExecutor == null) {
    animationExecutor = GlideExecutor.newAnimationExecutor();
  }

  if (memorySizeCalculator == null) {
    memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();
  }

  if (connectivityMonitorFactory == null) {
    connectivityMonitorFactory = new DefaultConnectivityMonitorFactory();
  }

  if (bitmapPool == null) {
    int size = memorySizeCalculator.getBitmapPoolSize();
    if (size > 0) {
      bitmapPool = new LruBitmapPool(size);
    } else {
      bitmapPool = new BitmapPoolAdapter();
    }
  }

  if (arrayPool == null) {
    arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes());
  }

  if (memoryCache == null) {
    memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
  }

  if (diskCacheFactory == null) {
    diskCacheFactory = new InternalCacheDiskCacheFactory(context);
  }

  if (engine == null) {
    engine =
        new Engine(
            memoryCache,
            diskCacheFactory,
            diskCacheExecutor,
            sourceExecutor,
            GlideExecutor.newUnlimitedSourceExecutor(),
            animationExecutor,
            isActiveResourceRetentionAllowed);
  }

  if (defaultRequestListeners == null) {
    defaultRequestListeners = Collections.emptyList();
  } else {
    defaultRequestListeners = Collections.unmodifiableList(defaultRequestListeners);
  }

  GlideExperiments experiments = glideExperimentsBuilder.build();
  RequestManagerRetriever requestManagerRetriever =
      new RequestManagerRetriever(requestManagerFactory, experiments);

  return new Glide(
      context,
      engine,
      memoryCache,
      bitmapPool,
      arrayPool,
      requestManagerRetriever,
      connectivityMonitorFactory,
      logLevel,
      defaultRequestOptionsFactory,
      defaultTransitionOptions,
      defaultRequestListeners,
      experiments);
}
Copy the code

Many other instances are created along with the Glide object, including cache executor SourceExecutor and DiskCacheExecutor, load Engine, RequestManagerRetriever, and more.

RequestManager create

com.bumptech.glide.manager.RequestManagerRetriever#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()); } } return getApplicationManager(context); }Copy the code

There is a lot of Context differentiation here, just to get different life cycle types:

  • In the main thread and notApplicationJudge yesFragmentActivityorActivityNeither is gettingBaseContextCome back in and judge again;
  • The rest is as followsApplicationTo bind the life cycle.

We select the FragmentActivity branch to continue:

@NonNull public RequestManager get(@NonNull FragmentActivity activity) { if (Util.isOnBackgroundThread()) { return get(activity.getApplicationContext()); } else { assertNotDestroyed(activity); frameWaiter.registerSelf(activity); FragmentManager fm = activity.getSupportFragmentManager(); Return supportFragmentGet(Activity, FM, /*parentHint=*/ NULL, isActivityVisible(Activity)); } } @NonNull private RequestManager supportFragmentGet( @NonNull Context context, @NonNull FragmentManager fm, @Nullable Fragment parentHint, boolean isParentVisible) { SupportRequestManagerFragment current = getSupportRequestManagerFragment(fm, parentHint); (2) RequestManager RequestManager = current. GetRequestManager (); ③ if (requestManager == null) {// TODO(b/27524013): Factor out this Glide.get() call. Glide glide = Glide.get(context); requestManager = factory.build( glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context); ④ if (isParentVisible) {requestManager.onstart (); } current.setRequestManager(requestManager); (5)} return requestManager; }Copy the code
  • Step 1 FetchActivityThe correspondingFragmentManager;
  • Step 2 According toFragmentManagerTo obtain the correspondingFragmentIf a new one is not createdSupportRequestManagerFragmentObject and put intoMap; If it already exists, directly fromMapExtract;
  • Step three, fromFragmentTo deriveRequestManagerObject;
  • Step 4: Create a new object if you don’t get it. Step 5: create a new objectRequestManagerandFragmentBinding.

SupportRequestManagerFragment here is actually an empty layout fragments, main effect is the perceptual Activity lifecycle.

RequestManager.load(url)

com.bumptech.glide.RequestManager#load(java.lang.String) public RequestBuilder<Drawable> load(@Nullable String string) {  return asDrawable().load(string); } public RequestBuilder<Drawable> asDrawable() { return as(Drawable.class); } public <ResourceType> RequestBuilder<ResourceType> as( @NonNull Class<ResourceType> resourceClass) { return new RequestBuilder<>(glide, this, resourceClass, context); } public RequestBuilder<TranscodeType> load(@Nullable String string) { return loadGeneric(string); } private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) { if (isAutoCloneEnabled()) { return clone().loadGeneric(model); } this.model = model; isModelSet = true; return selfOrThrowIfLocked(); }Copy the code

This process has relatively little logic, and basically creates a RequestBuilder object with a generic parameter of type Drawable, because what we’re analyzing is loading the Url into the specified ImageView.

RequestBuilder.into(iv)

com.bumptech.glide.RequestBuilder#into(ImageView) public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) { Util.assertMainThread(); Preconditions.checkNotNull(view); BaseRequestOptions<? > requestOptions = this; if (! requestOptions.isTransformationSet() && requestOptions.isTransformationAllowed() && view.getScaleType() ! = null) {switch (view.getScaleType()) {① case CENTER_CROP: requestOptions = requestOptions.clone().optionalCenterCrop(); break; . Omit the other zoom type}} return into (glideContext. BuildImageViewTarget (view, transcodeClass), / * targetListener = * / null, requestOptions, Executors.mainThreadExecutor()); (2)}Copy the code
  • First viewImageViewWhether the zoom type is set, if anyRequestOptions;
  • Enter theinto()Method, where four parameters are passed:
    • The first oneImageViewThe target, which contains the resource type, isDrawable.class;
    • The second is to request a listenRequestListenerContains two callbacks: load failure and resource ready to complete.
    • The third is the option to requestRequestOptions;
    • The fourth thread pool is called back to the main thread.

into(target,reqeuestListener,options,callbackExecutor)

com.bumptech.glide.RequestBuilder#into 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()"); } Request request = buildRequest(target, targetListener, options, callbackExecutor); ① Request previous = target.getrequest (); ② if (request. IsEquivalentTo (previous) &&! IsSkipMemoryCacheWithCompletePreviousRequest (options, previous)) {(3) if (! Preconditions.checkNotNull(previous).isRunning()) { previous.begin(); } return target; } requestManager.clear(target); target.setRequest(request); requestManager.track(target, request); (4) return target; }Copy the code
  • The first step is to build a Request object, which we’ll focus on below.

  • The second step is to retrieve the existing request from the Target object.

  • Step 3 Determine whether existing requests can be reused:

    • The first condition isrequestandpreviousWhether the parameters and request types are consistent;
    • Whether cache skipping is set andpreviousIt’s done.
  • The fourth step, if not reusable, uses the built Request object and finally calls the requestManager.track (target, Request) method.

Next, we pause the main flow and enter the logic that builds the Request object.

buildRequest(target,targetListener,opstions,callbackExecutor)

com.bumptech.glide.RequestBuilder#buildRequest private Request buildRequest( Target<TranscodeType> target, @Nullable RequestListener<TranscodeType> targetListener, BaseRequestOptions<? > requestOptions, Executor callbackExecutor) { return buildRequestRecursive( /*requestLock=*/ new Object(), target, targetListener, /*parentCoordinator=*/ null, transitionOptions, requestOptions.getPriority(), requestOptions.getOverrideWidth(), requestOptions.getOverrideHeight(), requestOptions, callbackExecutor); }Copy the code

The buildRequestRecursive() method is called directly here.

com.bumptech.glide.RequestBuilder#buildRequestRecursive private Request buildRequestRecursive( Object requestLock, Target<TranscodeType> target, @Nullable RequestListener<TranscodeType> targetListener, @Nullable RequestCoordinator parentCoordinator, TransitionOptions<? ,? super TranscodeType> transitionOptions, Priority priority, int overrideWidth, int overrideHeight, BaseRequestOptions<? > requestOptions, Executor callbackExecutor) { // Build the ErrorRequestCoordinator first if necessary so we can update parentCoordinator.  ErrorRequestCoordinator errorRequestCoordinator = null; if (errorBuilder ! = null) {① errorRequestCoordinator = new errorRequestCoordinator (requestLock, parentCoordinator); parentCoordinator = errorRequestCoordinator; } Request mainRequest = buildThumbnailRequestRecursive( requestLock, target, targetListener, parentCoordinator, transitionOptions, priority, overrideWidth, overrideHeight, requestOptions, callbackExecutor); ② if (errorRequestCoordinator == null) {③ return mainRequest; } int errorOverrideWidth = errorBuilder.getOverrideWidth(); int errorOverrideHeight = errorBuilder.getOverrideHeight(); if (Util.isValidDimensions(overrideWidth, overrideHeight) && ! errorBuilder.isValidOverride()) { errorOverrideWidth = requestOptions.getOverrideWidth(); errorOverrideHeight = requestOptions.getOverrideHeight(); } Request errorRequest = errorBuilder.buildRequestRecursive( requestLock, target, targetListener, errorRequestCoordinator, errorBuilder.transitionOptions, errorBuilder.getPriority(), errorOverrideWidth, errorOverrideHeight, errorBuilder, callbackExecutor); errorRequestCoordinator.setRequests(mainRequest, errorRequest); (4) return errorRequestCoordinator; }Copy the code
  • Step one: JudgeerrorBuilderIs it empty? This oneerrorBuilderIs that we passedRequestBuilder.error()If not null, a coordinator will be generatedErrorRequestCoordinatorTo coordinate the followingmainRequestanderrorRequest, now we do not set by default, just look at the main flow;
  • Step 2 PassbuildThumbnailRequestRecursive()generatemainRequest;
  • Step 3 IferrorRequestCoordinatorReturns directly if nullmainRequest;
  • Step 4 IferrorRequestCoordinatorOne will be generated if it is not emptyerrorRequest.

Next in the main flow we look at how mainRequest is generated.

buildThumbnailRequestRecursive()

com.bumptech.glide.RequestBuilder#buildThumbnailRequestRecursive private Request buildThumbnailRequestRecursive( Object requestLock, Target<TranscodeType> target, RequestListener<TranscodeType> targetListener, @Nullable RequestCoordinator parentCoordinator, TransitionOptions<? ,? super TranscodeType> transitionOptions, Priority priority, int overrideWidth, int overrideHeight, BaseRequestOptions<? > requestOptions, Executor callbackExecutor) { if (thumbnailBuilder ! = null) { // Recursive case: contains a potentially recursive thumbnail request builder. if (isThumbnailBuilt) { throw new IllegalStateException( "You cannot use a request as both the main request and a " + "thumbnail, consider using clone() on the request(s) passed to thumbnail()"); } TransitionOptions<? ,? super TranscodeType> thumbTransitionOptions = thumbnailBuilder.transitionOptions; // Apply our transition by default to thumbnail requests but avoid overriding custom options // that may have been applied on the thumbnail request explicitly. if (thumbnailBuilder.isDefaultTransitionOptionsSet) { thumbTransitionOptions = transitionOptions; } Priority thumbPriority = thumbnailBuilder.isPrioritySet() ? thumbnailBuilder.getPriority() : getThumbnailPriority(priority); int thumbOverrideWidth = thumbnailBuilder.getOverrideWidth(); int thumbOverrideHeight = thumbnailBuilder.getOverrideHeight(); if (Util.isValidDimensions(overrideWidth, overrideHeight) && ! thumbnailBuilder.isValidOverride()) { thumbOverrideWidth = requestOptions.getOverrideWidth(); thumbOverrideHeight = requestOptions.getOverrideHeight(); } ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(requestLock, parentCoordinator); Request fullRequest = obtainRequest( requestLock, target, targetListener, requestOptions, coordinator, transitionOptions, priority, overrideWidth, overrideHeight, callbackExecutor); isThumbnailBuilt = true; // Recursively generate thumbnail requests. Request thumbRequest = thumbnailBuilder.buildRequestRecursive( requestLock, target, targetListener, coordinator, thumbTransitionOptions, thumbPriority, thumbOverrideWidth, thumbOverrideHeight, thumbnailBuilder, callbackExecutor); isThumbnailBuilt = false; coordinator.setRequests(fullRequest, thumbRequest); return coordinator; } else if (thumbSizeMultiplier ! = null) { // Base case: thumbnail multiplier generates a thumbnail request, but cannot recurse. ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(requestLock, parentCoordinator); Request fullRequest = obtainRequest( requestLock, target, targetListener, requestOptions, coordinator, transitionOptions, priority, overrideWidth, overrideHeight, callbackExecutor); BaseRequestOptions<? > thumbnailOptions = requestOptions.clone().sizeMultiplier(thumbSizeMultiplier); Request thumbnailRequest = obtainRequest( requestLock, target, targetListener, thumbnailOptions, coordinator, transitionOptions, getThumbnailPriority(priority), overrideWidth, overrideHeight, callbackExecutor); coordinator.setRequests(fullRequest, thumbnailRequest); return coordinator; } else { // Base case: no thumbnail. return obtainRequest( requestLock, target, targetListener, requestOptions, parentCoordinator, transitionOptions, priority, overrideWidth, overrideHeight, callbackExecutor); }}Copy the code

We’ll ignore the thumbnail Settings for now and look at the last else branch, which returns the value of the obtainRequest() method.

com.bumptech.glide.RequestBuilder#obtainRequest private Request obtainRequest( Object requestLock, Target<TranscodeType> target, RequestListener<TranscodeType> targetListener, BaseRequestOptions<? > requestOptions, RequestCoordinator requestCoordinator, TransitionOptions<? ,? super TranscodeType> transitionOptions, Priority priority, int overrideWidth, int overrideHeight, Executor callbackExecutor) { return SingleRequest.obtain( context, glideContext, requestLock, model, transcodeClass, requestOptions, overrideWidth, overrideHeight, priority, target, targetListener, requestListeners, requestCoordinator, glideContext.getEngine(), transitionOptions.getTransitionFactory(), callbackExecutor); }Copy the code

And when WE stop here, we can finally see that we actually went through many layers and ended up with a SingleRequest object. Then we need to jump back to the last line in the into() method, RequestManager.track(target,request), where request is the SingleRequest object we just saw.

RequestManager.track(target,request)

com.bumptech.glide.RequestManager#track synchronized void track(@NonNull Target<? > target, @NonNull Request request) { targetTracker.track(target); 1) requestTracker. RunRequest (request); (2)} com. Bumptech. Glide. Manager. TargetTracker# track private final Set < Target <? >> targets = Collections.newSetFromMap(new WeakHashMap<Target<? >, Boolean>()); public void track(@NonNull Target<? > target) { targets.add(target); } com.bumptech.glide.manager.RequestTracker#runRequest private final Set<Request> requests = Collections.newSetFromMap(new WeakHashMap<Request, Boolean>()); private final Set<Request> pendingRequests = new HashSet<>(); public void runRequest(@NonNull Request request) { requests.add(request); if (! isPaused) { request.begin(); } else { request.clear(); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Paused, delaying request"); } pendingRequests.add(request); }}Copy the code

Synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized

  • The first step totargetAdded to theTargetTracker.targetsIn the collection;
  • Step 2 RunSingleRequest, first of all, willSingleRequestAdded to theRequestTracker.requestsCollection, if the current state is not paused, directly calledSingleRequest.begin()Method, if the current state is paused, thenSingleRequestAdd to the collection to be determinedpendingRequests.

Let’s move on to the singlerequest.begin () method.

SingleRequest.begin()

com.bumptech.glide.request.SingleRequest#begin public void begin() { synchronized (requestLock) { assertNotCallingCallbacks(); stateVerifier.throwIfRecycled(); startTime = LogTime.getLogTime(); If (model == null) {① if (util. isValidDimensions(overrideWidth, overrideHeight)) {width = overrideWidth; height = overrideHeight; } int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG; onLoadFailed(new GlideException("Received null model"), logLevel); return; } if (status == status. RUNNING) {② throw new IllegalArgumentException("Cannot restart a RUNNING request"); } if (status == Status.COMPLETE) {③ onResourceReady(resource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false); return; } experimentalNotifyRequestStarted(model); cookie = GlideTrace.beginSectionAsync(TAG); status = Status.WAITING_FOR_SIZE; If (Util. IsValidDimensions (overrideWidth, overrideHeight)) {④ onSizeReady(overrideWidth, overrideHeight); } else { target.getSize(this); } the if ((status = = status. RUNNING | | status = = status. WAITING_FOR_SIZE) && canNotifyStatusChanged ()) {(5) target.onLoadStarted(getPlaceholderDrawable()); } if (IS_VERBOSE_LOGGABLE) { logV("finished run method in " + LogTime.getElapsedMillis(startTime)); }}}Copy the code

In the entire begin() method, we break it down into five steps:

  • The first stepmodelThat isurlIf it is empty, return directly, callbackonLoadFailed();
  • The second step is to determine if the current state isRUNNINGState, throw exception directly, oneRequestOnly one execution can be performed at the same time.
  • Step 3 Determine if the current state isCOMMPLETEState, then you can return it directlyresource.onResourceReady()We’ll get that laterDrawableDisplays to the specifiedImageViewIn, this will be parsed later;
  • The fourth step is to judge whether the width and height are effective. If so, enter directlyonSizeReady()Method, enable load mode,The state will enterRUNNINGstate;
  • Step 5 If the current state isRUNNINGorWAITING_FOR_SIZEState to display a placeholder map.

SingleRequest.onSizeReady(width,height)

com.bumptech.glide.request.SingleRequest#onSizeReady public void onSizeReady(int width, int height) { stateVerifier.throwIfRecycled(); synchronized (requestLock) { if (status ! = Status.WAITING_FOR_SIZE) { return; } status = Status.RUNNING; 1) float sizeMultiplier = requestOptions. GetSizeMultiplier (); this.width = maybeApplySizeMultiplier(width, sizeMultiplier); this.height = maybeApplySizeMultiplier(height, sizeMultiplier); loadStatus = engine.load( glideContext, model, requestOptions.getSignature(), this.width, this.height, requestOptions.getResourceClass(), transcodeClass, priority, requestOptions.getDiskCacheStrategy(), requestOptions.getTransformations(), requestOptions.isTransformationRequired(), requestOptions.isScaleOnlyOrNoTransform(), requestOptions.getOptions(), requestOptions.isMemoryCacheable(), requestOptions.getUseUnlimitedSourceGeneratorsPool(), requestOptions.getUseAnimationPool(), requestOptions.getOnlyRetrieveFromCache(), this, callbackExecutor); (2)}}Copy the code
  • The first step is to set the state toRUNNING;
  • Start theEngineThe engine loads the image.

Engine.load()

com.bumptech.glide.load.engine.Engine#load 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 ? LogTime.getLogTime() : 0; EngineKey key = keyFactory.buildKey( model, signature, width, height, transformations, resourceClass, transcodeClass, options); 1) EngineResource <? > memoryResource; synchronized (this) { memoryResource = loadFromMemory(key, isMemoryCacheable, startTime); (2) 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); }} // calling back while holding the engine lock, doing so makes it easier for callers to // deadlock. cb.onResourceReady( memoryResource, DataSource.MEMORY_CACHE, false); (4) return null; }Copy the code
  • Step 1 GenerationEngineKeyIs used for subsequent write cache or read cache.
  • The second step is to look in the cache, first from the active cache ActivityResource and then from the MemoryCache MemoryCache.
  • Step 3 If there is no hit in the memory cache, start a new task

Engine.waitForExistingOrStartNewJob()

com.bumptech.glide.load.engine.Engine#waitForExistingOrStartNewJob private <R> LoadStatus waitForExistingOrStartNewJob(...) {// Create EngineJob<? > current = jobs.get(key, onlyRetrieveFromCache); 1) if (current! = null) { current.addCallback(cb, callbackExecutor); if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Added to existing load", startTime, key); } return new LoadStatus(cb, current); } // Create EngineJob EngineJob<R> EngineJob = engineJobFactory.build(key, isMemoryCacheable, useUnlimitedSourceExecutorPool, useAnimationPool, onlyRetrieveFromCache); // Create DecodeJob, // create DecodeJob DecodeJob<R> DecodeJob = decodeJobFactory. Build (glideContext, model, key, signature, width, height, resourceClass, transcodeClass, priority, diskCacheStrategy, transformations, isTransformationRequired, isScaleOnlyOrNoTransform, onlyRetrieveFromCache, options, engineJob); // Save to Jobs map. Put (key, engineJob); // If the callback resource is loaded, CallbackExecutor is a callback to the main thread engineJob.addCallback(cb, callbackExecutor); Runnable enginejob.start (decodeJob); Runnable enginejob.start (decodeJob); ④ if (VERBOSE_IS_LOGGABLE) {logWithTimeAndKey("Started new Load ", startTime, key); } return new LoadStatus(cb, engineJob); }Copy the code
  • The first step is to find out if it already existsJob, if there is a direct return;
  • Step 2 CreateEngineJobObject to start a task and call back the success and failure of resource loading;
  • Step 3 CreateDecodeJobObject for loading resources, operating caches, and so on, and at this timeDecodeJobAlready andEngineJobIn order toCallbackForms bound together;
  • Step 4 starts executionDecodeJobtherun()Method,EngineJob.start()Inside is what calls the thread poolexcute()Method, passed in exactlyDecodeJobSo we go inDecodeJob.run()Methods Continue to analyze.

DecodeJob.run()

com.bumptech.glide.load.engine.DecodeJob#run public void run() { // This should be much more fine grained, but since Java's thread pool implementation silently // swallows all otherwise fatal exceptions, this will at least make it obvious to developers // that something is failing. GlideTrace.beginSectionFormat("DecodeJob#run(reason=%s, model=%s)", runReason, model); // Methods in the try statement can invalidate currentFetcher, so set a local variable here to // ensure that the fetcher is cleaned up either way. DataFetcher<? > localFetcher = currentFetcher; Try {// If (isCancelled) {notifyFailed(); return; } // the next logical step runWrapped(); } catch (CallbackException e) {throw e; } finally { if (localFetcher ! = null) {localfetcher.cleanup (); } GlideTrace.endSection(); }}Copy the code

The run() method goes directly inside the runWrapped() method.

DecodeJob#runWrapped

com.bumptech.glide.load.engine.DecodeJob#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); } } 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); } } 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); }} Private enum RunReason {/** First commit task */ INITIALIZE, /** Attempt to switch from disk cache to memory cache */ SWITCH_TO_SOURCE_SERVICE, /** * Decode original data, */ DECODE_DATA,}Copy the code

Take a look at the enumeration class RunReason, which has three states:

  • INITIALIZE: represents the first time to submit a task;
  • SWITCH_TO_SOURCE_SERVICE: There is in disk cache, cut to memory cache;
  • DECODE_DATA: Load the resource directly (missed cache).

Then we look at the runWrapped() method, where the runReason variable is assigned the INITIALIZE state in init(). So let’s look at the first case: the default cache is fully open

  • First of all bygetNextStage(Stage.INITIALIZE)Gets the next stage, the default cache is fully on, the next state isStage.RESOURCE_CACHE;
  • Then throughgetNextGenerator()Get to the next oneDataFetcherGeneratorBecause of the aboveStageisRESOURCE_CACHE, so the return isResourceCacheGeneratortheGeneratorIs from the disk cache to obtain whether there is a cache after decoding, transcoding;
  • The last callrunGenerators().

DecodeJob#runGenerators

com.bumptech.glide.load.engine.DecodeJob#runGenerators private void runGenerators() { currentThread = Thread.currentThread(); startFetchTime = LogTime.getLogTime(); boolean isStarted = false; // while loop ends with stage==SOURCE 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

Here we can see, into a while loop, including currentGenerator. StartNext () is to obtain ResourceCacheGenerator whether existing cache, if there is returned directly, so there is no continue to go down, Until stage= stage.source, then call the reschedule() method.

DecodeJob#reschedule

Com. Bumptech. Glide. The load. The engine. DecodeJob# reschedule public void reschedule () {/ / will first runReason for SWITCH_TO_SOURCE_SERVICE  runReason = RunReason.SWITCH_TO_SOURCE_SERVICE; // Then call callback.reschedule(this) callback.reschedule(this); }Copy the code
  • Step 1 ChangerunReasonValue, set toSWITCH_TO_SOURCE_SERVICE;
  • Second stepcallback.reschedule()Method, created earlierDecodeJobI mentioned the object herecallbackisEngineJobObject, so we have to go back toEngineJob.reschedule()Methods.

EngineJob#reschedule

com.bumptech.glide.load.engine.EngineJob#reschedule 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

Get the sourceExecutor thread pool and execute the decodeJob.run () method… Have a little dizzy

But there are several states we need to know. For DecodeJob, its runReason is already SWITCH_TO_SOURCE_SERVICE and stage is SOURCE.

After that it goes back to the decodeJob.run () method and goes to DataSourceGenerator to see if there is an active file cache. If there is no active file cache, it goes to SourceGenerator to load the resource.

Summary loading process:

  • From the firstResourceCacheGeneratorTo find whether there is a converted, decoded disk file cache, the cache is smaller than the source file cache;
  • Again fromDataSourceGeneratorFind the source file disk cache, this is loaded source files directly write cache;
  • If all else fails, goHttpRequest resources, the resource request after the first written to the disk cache, and then returned toTarget.

The onResourceReady() callback method is called when the resource is retrieved. The callback chain for this method is: SingleRequest#onResourceReady->ImageViewTarget#onResourceReady->mageViewTarget#setResourceInternal->DrawableImageViewTar get#setResource

com.bumptech.glide.request.target.DrawableImageViewTarget#setResource

protected void setResource(@Nullable Drawable resource) {
  view.setImageDrawable(resource);
}
Copy the code

Finally, in the DrawableImageViewTarget, we lower the Resource setting to the ImageView.

To make it easier for you to analyze the source code, I put method references at the top of the code block for your reference.


Glide source code is not easy to eat a lot of time, you can read more to see, understand the work of each object, and then to repeatedly understand, loading logic involved in the cache to find the load of resources, clear after will feel suddenly open, come on ~