use

1. Add dependencies

Implementation 'com. Making. Bumptech. Glide: glide: 4.12.0' annotationProcessor 'com. Making. Bumptech. Glide: the compiler: 4.12.0'Copy the code

2. Basic use

  1. Loading pictures
Glide.with(this)
    .load(imgUrl)
    .into(imageView1)
Copy the code
  1. Cancel the loading
Glide.with(this).clear(imageView1)
Copy the code
  • In fact, When an Activity or Fragment instance passed in Glide. With () is destroyed, Glide automatically unloads and recycles the resource;

3. Advanced operations

  1. More optional configurations
Green. With (this)// Context object context.load (imgUrl)// image address.placeholder(r.map.ic_launcher)// Placeholder map before loading .error(r.map.ic_launcher)// placeholder map that failed to load. Override (150,100)// specify the image size (usually not set, Glide automatically determines the image size based on the ImageView size. SkipMemoryCache (true)// skipMemoryCache DiskCacheStrategy (diskCacheStrategy. AUTOMATIC) / / resources according to the pictures intelligently choose to use which kind of caching strategy (the default option) .diskCacheStrategy(diskCacheStrategy.all) Also cache the image converted. DiskCacheStrategy (diskCacheStrategy. DATA) / / cache only. The original image diskCacheStrategy (diskCacheStrategy. RESOURCE)/images/cache only transformation .diskCacheStrategy(diskCacheStrategy.none)// Disable disk caching. Priority (priority.high)// priority. Thumbnail ( Glide. With (this).load(imgUrl).override(20))// Set thumbnail.Copy the code
  • For more optional configurations, refer to the official documentation
  1. Share the configuration through RequestOptions
val options = RequestOptions()
    .placeholder(R.mipmap.ic_launcher)
    .error(R.mipmap.ic_launcher)
Glide.with(this)
    .load(imgUrl)
    .apply(options)
    .into(imageView1)
Glide.with(this)
    .load(imgUrl)
    .apply(options)
    .into(imageView2)
Copy the code

The source code parsing

  • Source code mainly from the most basic three methods: with, load, into

1. with

  • You can use the getRetriever method to obtain the RequestManagerRetriever object and the GET method to obtain the RequestManager instance
public static RequestManager with(@NonNull Context context) {
    return getRetriever(context).get(context);
}

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

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

public static RequestManager with(@NonNull Fragment fragment) {
    return getRetriever(fragment.getContext()).get(fragment);
}

public static RequestManager with(@NonNull android.app.Fragment fragment) {
    return getRetriever(fragment.getActivity()).get(fragment);
}

public static RequestManager with(@NonNull View view) {
    return getRetriever(view.getContext()).get(view);
}
Copy the code
  • In the getRetriever method, use Glide. Get to obtain the Glide singleton using lazy + double checklock, and use the getter method to obtain the requestManagerRetriever attribute.
private static RequestManagerRetriever getRetriever(@Nullable Context context) {
    Preconditions.checkNotNull(
        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 Glide.get(context).getRequestManagerRetriever();
}
Copy the code

Get Glide instance

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
  • To actually create and initialize a Glide instance, use the initializeGlide method and call the glideBuilder. build method to create a Glide instance in builder mode
private static void checkAndInitializeGlide( @NonNull Context context, @Nullable GeneratedAppGlideModule generatedAppGlideModule) { if (isInitializing) { throw new IllegalStateException( "You  cannot call Glide.get() in registerComponents()," + " use the provided Glide instance instead"); } isInitializing = true; initializeGlide(context, generatedAppGlideModule); isInitializing = false; } private static void initializeGlide( @NonNull Context context, @Nullable GeneratedAppGlideModule generatedAppGlideModule) { initializeGlide(context, new GlideBuilder(), generatedAppGlideModule); } private static void initializeGlide( @NonNull Context context, @NonNull GlideBuilder builder, @Nullable GeneratedAppGlideModule annotationGeneratedModule) { Context applicationContext = context.getApplicationContext(); . Glide glide = builder.build(applicationContext); . Glide.glide = glide; }Copy the code
  • The glideBuilder.build method code is as follows
Glide build(@nonNULL Context Context) {if (sourceExecutor == null) {sourceExecutor = GlideExecutor.newSourceExecutor(); The thread pool if} / / disk cache (diskCacheExecutor = = null) {diskCacheExecutor = GlideExecutor. NewDiskCacheExecutor (); } / / animation thread pool if (animationExecutor = = null) {animationExecutor = GlideExecutor. NewAnimationExecutor (); } // Create memory size controller if (memorySizeCalculator == null) {memorySizeCalculator = new MemorySizeCalculator.Builder(context).build(); } / / create a network to monitor factory if (connectivityMonitorFactory = = null) {connectivityMonitorFactory = new DefaultConnectivityMonitorFactory(); } if (bitmapPool == null) { int size = memorySizeCalculator.getBitmapPoolSize(); If (size > 0) {// Create a bitmap object pool bitmapPool = new LruBitmapPool(size); } else {// Do not use cache bitmapPool = new BitmapPoolAdapter(); }} / / create the object array buffer pool if (arrayPool = = null) {arrayPool = new LruArrayPool (memorySizeCalculator. GetArrayPoolSizeInBytes ()); } / / create the memory cache if (memoryCache = = null) {memoryCache = new LruResourceCache (memorySizeCalculator. GetMemoryCacheSize ()); } / / create a hard disk cache if factory (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(); // Create a request management class whose constructor instantiates a main thread handler object. RequestManagerRetriever = new RequestManagerRetriever(requestManagerFactory, experiments); return new Glide( context, engine, memoryCache, bitmapPool, arrayPool, requestManagerRetriever, connectivityMonitorFactory, logLevel, defaultRequestOptionsFactory, defaultTransitionOptions, defaultRequestListeners, experiments); }Copy the code
  • The RequestManagerRetriever constructor in the code above is as follows
public RequestManagerRetriever( @Nullable RequestManagerFactory factory, GlideExperiments experiments) { this.factory = factory ! = null ? factory : DEFAULT_FACTORY; handler = new Handler(Looper.getMainLooper(), this /* Callback */); frameWaiter = buildFrameWaiter(experiments); }Copy the code

RequestManagerRetriever.get

  • The Glide singleton is used to obtain the RequestManagerRetriever instance, and the GET method is used to obtain the RequestManager instance
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 && ((ContextWrapper) context).getBaseContext().getApplicationContext() ! = null) { return get(((ContextWrapper) context).getBaseContext()); } } return getApplicationManager(context); } public RequestManager get(@NonNull FragmentActivity activity) { if (Util.isOnBackgroundThread()) { return get(activity.getApplicationContext()); } else { ... FragmentManager fm = activity.getSupportFragmentManager(); return supportFragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity)); } } public RequestManager get(@NonNull Fragment fragment) { ... if (Util.isOnBackgroundThread()) { return get(fragment.getContext().getApplicationContext()); } else { ... FragmentManager fm = fragment.getChildFragmentManager(); return supportFragmentGet(fragment.getContext(), fm, fragment, fragment.isVisible()); } } public RequestManager get(@NonNull Activity activity) { if (Util.isOnBackgroundThread()) { return get(activity.getApplicationContext()); } else if (activity instanceof FragmentActivity) { return get((FragmentActivity) activity); } else { ... android.app.FragmentManager fm = activity.getFragmentManager(); return fragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity)); } } public RequestManager get(@NonNull View view) { ... Activity activity = findActivity(view.getContext()); if (activity == null) { return get(view.getContext().getApplicationContext()); } if (activity instanceof FragmentActivity) { Fragment fragment = findSupportFragment(view, (FragmentActivity) activity); return fragment ! = null ? get(fragment) : get((FragmentActivity) activity); } android.app.Fragment fragment = findFragment(view, activity); if (fragment == null) { return get(activity); } return get(fragment); }Copy the code
  • We end up calling supportFragmentGet or fragmentGet to get the requestManager instance, which creates a Fragment, so we know that Glide manages the life cycle by maintaining a Fragment internally
private RequestManager supportFragmentGet(
      @NonNull Context context,
      @NonNull FragmentManager fm,
      @Nullable Fragment parentHint,
      boolean isParentVisible) {
    SupportRequestManagerFragment current = getSupportRequestManagerFragment(fm, parentHint);
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
      Glide glide = Glide.get(context);
      requestManager =
          factory.build(
              glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
      if (isParentVisible) {
        requestManager.onStart();
      }
      current.setRequestManager(requestManager);
    }
    return requestManager;
}

private RequestManager fragmentGet(
      @NonNull Context context,
      @NonNull android.app.FragmentManager fm,
      @Nullable android.app.Fragment parentHint,
      boolean isParentVisible) {
    RequestManagerFragment current = getRequestManagerFragment(fm, parentHint);
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
      Glide glide = Glide.get(context);
      requestManager =
          factory.build(
              glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
      if (isParentVisible) {
        requestManager.onStart();
      }
      current.setRequestManager(requestManager);
    }
    return requestManager;
}

Copy the code

2. load

  • Load is a requestManager. load method that has multiple overloaded methods and can pass in different image sources
public RequestBuilder<Drawable> load(@Nullable Bitmap bitmap) {
    return asDrawable().load(bitmap);
}
public RequestBuilder<Drawable> load(@Nullable Drawable drawable) {
    return asDrawable().load(drawable);
}
public RequestBuilder<Drawable> load(@Nullable String string) {
    return asDrawable().load(string);
}
public RequestBuilder<Drawable> load(@Nullable Uri uri) {
    return asDrawable().load(uri);
}
public RequestBuilder<Drawable> load(@Nullable File file) {
    return asDrawable().load(file);
}
public RequestBuilder<Drawable> load(@RawRes @DrawableRes @Nullable Integer resourceId) {
    return asDrawable().load(resourceId);
}
public RequestBuilder<Drawable> load(@Nullable URL url) {
    return asDrawable().load(url);
}
public RequestBuilder<Drawable> load(@Nullable byte[] model) {
    return asDrawable().load(model);
}
public RequestBuilder<Drawable> load(@Nullable Object model) {
    return asDrawable().load(model);
}
Copy the code
  • All of the above code calls asDrawable() first, so let’s see what it is

asDrawable()

  • The asDrawable () method is called and Drawable. Class is passed to the RequestBuilder
public RequestBuilder<Drawable> asDrawable() {
    return as(Drawable.class);
}

public <ResourceType> RequestBuilder<ResourceType> as(
      @NonNull Class<ResourceType> resourceClass) {
    return new RequestBuilder<>(glide, this, resourceClass, context);
}
Copy the code
  • And what we found is that we also have asBitmap, asGif
public RequestBuilder<GifDrawable> asGif() {
    return as(GifDrawable.class).apply(DECODE_TYPE_GIF);
}

public RequestBuilder<Bitmap> asBitmap() {
    return as(Bitmap.class).apply(DECODE_TYPE_BITMAP);
}
Copy the code
  • Now we know that requestManager.load is done by getting the RequestBuilder instance and calling its load method

RequestBuilder.load

  • Requestbuilder.load also has multiple overloaded methods, but all of them call loadGeneric methods
public RequestBuilder<TranscodeType> load(@Nullable String string) {
    return loadGeneric(string);
}
public RequestBuilder<TranscodeType> load(@Nullable Uri uri) {
    return loadGeneric(uri);
}
public RequestBuilder<TranscodeType> load(@Nullable File file) {
    return loadGeneric(file);
}
public RequestBuilder<TranscodeType> load(@Nullable Drawable drawable) {
    return loadGeneric(drawable).apply(diskCacheStrategyOf(DiskCacheStrategy.NONE));
}
public RequestBuilder<TranscodeType> load(@Nullable Bitmap bitmap) {
    return loadGeneric(bitmap).apply(diskCacheStrategyOf(DiskCacheStrategy.NONE));
}
public RequestBuilder<TranscodeType> load(@RawRes @DrawableRes @Nullable Integer resourceId) {
    return loadGeneric(resourceId).apply(signatureOf(AndroidResourceSignature.obtain(context)));
}
Copy the code
  • LoadGeneric saves the image source, sets the source identifier isModelSet = true, and returns the RequestBuilder itself this
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) { if (isAutoCloneEnabled()) { return clone().loadGeneric(model); } this.model = model; isModelSet = true; return selfOrThrowIfLocked(); } protected final T selfOrThrowIfLocked() { if (isLocked) { throw new IllegalStateException("You cannot modify locked T,  consider clone()"); } return self(); } private T self() { return (T) this; }Copy the code

3. into

  • The next step is to set up the target View with the requestBuilder.into method
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) { Util.assertMainThread(); / / assertion is the Preconditions of in the main thread call checkNotNull (view); BaseRequestOptions<? > requestOptions = this; if (! requestOptions.isTransformationSet() && requestOptions.isTransformationAllowed() && view.getScaleType() ! = null) {// Familiar scaletypes switch (view.getScaleType()) {case CENTER_CROP: requestOptions = requestOptions.clone().optionalCenterCrop(); break; case CENTER_INSIDE: requestOptions = requestOptions.clone().optionalCenterInside(); break; case FIT_CENTER: case FIT_START: case FIT_END: requestOptions = requestOptions.clone().optionalFitCenter(); break; case FIT_XY: requestOptions = requestOptions.clone().optionalCenterInside(); break; case CENTER: case MATRIX: default: // Do nothing. } } return into( glideContext.buildImageViewTarget(view, transcodeClass), /*targetListener=*/ null, requestOptions, Executors.mainThreadExecutor()); }Copy the code
  • The buildImageViewTarget method is used to convert the view to target and transcodeClass to bitmap. class or Drawable
public <X> ViewTarget<ImageView, X> buildImageViewTarget( @NonNull ImageView imageView, @NonNull Class<X> transcodeClass) { return imageViewTargetFactory.buildTarget(imageView, transcodeClass); } public class ImageViewTargetFactory { @NonNull @SuppressWarnings("unchecked") public <Z> ViewTarget<ImageView, Z> buildTarget( @NonNull ImageView view, @NonNull Class<Z> clazz) { if (Bitmap.class.equals(clazz)) { return (ViewTarget<ImageView, Z>) new BitmapImageViewTarget(view); } else if (Drawable.class.isAssignableFrom(clazz)) { return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view); } else { throw new IllegalArgumentException( "Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)"); }}}Copy the code
  • The target into method has four inputs: target, listener, configuration information, and thread pool.

One request is created via buildRequest

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)) { if (! Preconditions.checkNotNull(previous).isRunning()) { previous.begin(); } return target; } // Break requestManager.clear(target); // And set it to a new request, target.setrequest (request); Requestmanager.track (target, request); return target; }Copy the code

requestManager.track

  • The track method code is as follows
synchronized void track(@NonNull Target<? > target, @NonNull Request request) { targetTracker.track(target); requestTracker.runRequest(request); Public void track(@nonnull Target<? > target) { targets.add(target); } // Call the begin method to process the request if the current state is not paused, 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

SingleRequest.begin

  • The buildRequest method is called SingleRequest. Obtain and the buildRequest method is called SingleRequest
public void begin() { synchronized (requestLock) { assertNotCallingCallbacks(); stateVerifier.throwIfRecycled(); startTime = LogTime.getLogTime(); . status = Status.WAITING_FOR_SIZE; If (Util. IsValidDimensions (overrideWidth, overrideHeight)) {// Pass the target view's width to onSizeReady. overrideHeight); } else { target.getSize(this); } if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE) && canNotifyStatusChanged()) { target.onLoadStarted(getPlaceholderDrawable()); } if (IS_VERBOSE_LOGGABLE) { logV("finished run method in " + LogTime.getElapsedMillis(startTime)); }}}Copy the code
  • The most important line in BEGIN is to call the onSizeReady method and pass in the width and height of the target view. The onSizeReady code looks like this
public void onSizeReady(int width, int height) { stateVerifier.throwIfRecycled(); synchronized (requestLock) { ... if (status ! = Status.WAITING_FOR_SIZE) { return; } // Change status to running status = status.running; float sizeMultiplier = requestOptions.getSizeMultiplier(); this.width = maybeApplySizeMultiplier(width, sizeMultiplier); this.height = maybeApplySizeMultiplier(height, sizeMultiplier); / / call the Engine. The load of the request processing loadStatus = Engine. The load (glideContext, model, requestOptions getSignature (), enclosing the 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); if (status ! = Status.RUNNING) { loadStatus = null; }}}Copy the code

Engine.load

  • As you can see, onSizeReady basically calls engine.load () to process the request.
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); EngineResource<? > memoryResource; Synchronized (this) {memoryResource = loadFromMemory(key, isMemoryCacheable, startTime); synchronized (this) {memoryResource = loadFromMemory(key, isMemoryCacheable, startTime); If (memoryResource = = null) {/ / the cache without waiting or open a new job get photo resources 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. cb.onResourceReady( memoryResource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false); return null; }Copy the code
  • In the Engine. The load is mainly through the first loadFromMemory retrieve images from the cache resources, taking less than you call waitForExistingOrStartNewJob

Engine.loadFromMemory

  • The loadFromMemory code is as follows
private EngineResource<? > loadFromMemory(EngineKey key, Boolean isMemoryCacheable, long startTime) {// Check whether to allow memory cache if (! isMemoryCacheable) { return null; } // Get EngineResource<? > active = loadFromActiveResources(key); if (active ! = null) { return active; } // Get EngineResource<? > cached = loadFromCache(key); if (cached ! = null) { return cached; } return null; }Copy the code
loadFromActiveResources
  • LoadFromActiveResources code is as follows, where ActiveResources maintains a HashMap

    whose value is a weak reference, and invokes its GET method to obtain image resources. If acquire, then call acquire to add one to the number of references of the object. The memory cache is operated by counting hash algorithm. If the count tree of the object is 0, it means that no other object references the resource and the resource can be released
    ,>
private EngineResource<? > loadFromActiveResources(Key key) { EngineResource<? > active = activeResources.get(key); if (active ! = null) { active.acquire(); } return active; } private final ActiveResources activeResources; final class ActiveResources { private final boolean isActiveResourceRetentionAllowed; private final Executor monitorClearedResourcesExecutor; @VisibleForTesting final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>(); private final ReferenceQueue<EngineResource<? >> resourceReferenceQueue = new ReferenceQueue<>(); private ResourceListener listener; private volatile boolean isShutdown; @Nullable private volatile DequeuedResourceCallback cb; . synchronized void activate(Key key, EngineResource<? > resource) { ResourceWeakReference toPut = new ResourceWeakReference( key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed); ResourceWeakReference removed = activeEngineResources.put(key, toPut); if (removed ! = null) { removed.reset(); } } synchronized EngineResource<? > get(Key key) { ResourceWeakReference activeRef = activeEngineResources.get(key); if (activeRef == null) { return null; } EngineResource<? > active = activeRef.get(); if (active == null) { cleanupActiveReference(activeRef); } return active; } } class EngineResource<Z> implements Resource<Z> { ... private int acquired; private boolean isRecycled; . synchronized void acquire() { if (isRecycled) { throw new IllegalStateException("Cannot acquire a recycled resource"); } ++acquired; }}Copy the code
loadFromCache
  • Continue back to loadFromMemory, if not in ActiveResources, call loadFromCache to try to fetch from lruCache, loadFromCache code is as follows, to fetch image resource from lruCache, Call Acquire to add one to the number of references to the object, and call Activeresources. activate to add it to activeResources
private EngineResource<? > loadFromCache(Key key) { EngineResource<? > cached = getEngineResourceFromCache(key); if (cached ! = null) { cached.acquire(); activeResources.activate(key, cached); } return cached; } private EngineResource<? > getEngineResourceFromCache Key (Key) {/ / for Lru data acquisition is by removing way to obtain the 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, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ true, key, /*listener=*/ this); } return result; }Copy the code
  • In the front of the cache in the code above is GlideBuilder. Build initialization memoryCache = new LruResourceCache (memorySizeCalculator. GetMemoryCacheSize ());
LruResourceCache
  • LruResourceCache inherits LruCache and implements MemoryCache interface. LruCache internally uses LinkedHashMap accessOrder equal to true to sort contents according to the order of access. A feature that puts the most recently accessed element at the end of a double-linked list is implemented as follows
public class LruResourceCache extends LruCache<Key, Resource<? >> implements MemoryCache { ... } public class LruCache<T, Y> {private final Map<T, Entry<Y>> Cache = new LinkedHashMap<>(100, 0.75f, true); private final long initialMaxSize; private long maxSize; private long currentSize; . public synchronized Y remove(@NonNull T key) { Entry<Y> entry = cache.remove(key); if (entry == null) { return null; } currentSize -= entry.size; return entry.value; }... } public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>{ public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; }... }Copy the code

waitForExistingOrStartNewJob

  • . Then back to the Engine load, take less than loadFromMemory, call the waitForExistingOrStartNewJob, code is as follows
final class Jobs { private final Map<Key, EngineJob<? >> jobs = new HashMap<>(); private final Map<Key, EngineJob<? >> onlyCacheJobs = new HashMap<>(); . } private <R> LoadStatus waitForExistingOrStartNewJob(...) {// Get EngineJob from jobs. EngineJob<? > current = jobs.get(key, onlyRetrieveFromCache); if (current ! = null) { current.addCallback(cb, callbackExecutor); Return new LoadStatus(cb, current); EngineJob 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); // Add the created engineJob to Jobs jobs.put(key, engineJob); engineJob.addCallback(cb, callbackExecutor); DecodeJob (decodeJob); decodeJob (decodeJob); return new LoadStatus(cb, engineJob); }Copy the code

engineJob.start

  • Start = enginejob. start = enginejob. start = enginejob. start
public synchronized void start(DecodeJob<R> decodeJob) { this.decodeJob = decodeJob; GlideExecutor executor = / / determine whether need disk cache, use the disk is thread pool, or use the memory cache thread pool decodeJob. WillDecodeFromCache ()? diskCacheExecutor : getActiveSourceExecutor(); // Execute job executor. Execute (decodeJob); }Copy the code
  • The initialization of diskCacheExecutor can be found in glideBuilder.build
diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();

public final class GlideExecutor implements ExecutorService {
    public static GlideExecutor newDiskCacheExecutor() {
        return newDiskCacheBuilder().build();
    }
    public static GlideExecutor.Builder newDiskCacheBuilder() {
        return new GlideExecutor.Builder(/*preventNetworkOperations=*/ true)
            .setThreadCount(DEFAULT_DISK_CACHE_EXECUTOR_THREADS)
            .setName(DEFAULT_DISK_CACHE_EXECUTOR_NAME);
    }
    GlideExecutor(ExecutorService delegate) {
        this.delegate = delegate;
    }
    public void execute(@NonNull Runnable command) {
        delegate.execute(command);
    }
    public static final class Builder {
        ...
        public GlideExecutor build() {
              if (TextUtils.isEmpty(name)) {
                throw new IllegalArgumentException(
                    "Name must be non-null and non-empty, but given: " + name);
              }
              ThreadPoolExecutor executor =
                  new ThreadPoolExecutor(
                      corePoolSize,
                      maximumPoolSize,
                      /*keepAliveTime=*/ threadTimeoutMillis,
                      TimeUnit.MILLISECONDS,
                      new PriorityBlockingQueue<Runnable>(),
                      new DefaultThreadFactory(name, uncaughtThrowableStrategy, preventNetworkOperations));

              if (threadTimeoutMillis != NO_THREAD_TIMEOUT) {
                executor.allowCoreThreadTimeOut(true);
              }

              return new GlideExecutor(executor);
          }
    }
}
Copy the code
  • Both disk cache and memory will eventually call run of DecodeJob(which inherits Runnable), followed by runWrapped
class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback, Runnable, Comparable<DecodeJob<? >>, Poolable { public void run() { ... try { if (isCancelled) { notifyFailed(); return; } runWrapped(); } catch ( ... } private void runWrapped() { switch (runReason) { case INITIALIZE: Stage.resource_cache 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); }}Copy the code
  • RunReason initializes DecodeJob to INITIALIZE INITIALIZE, so getNextGenerator is called, and getNextStage is RESOURCE_CACHE, So return ResourceCacheGenerator
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

runGenerators

  • RunGenerators will be called after DecodeJob. RunWrapped
private void runGenerators() { ... while (! isCancelled && currentGenerator ! = null && ! (isStarted = currentGenerator.startNext())) { stage = getNextStage(stage); currentGenerator = getNextGenerator(); if (stage == Stage.SOURCE) { reschedule(); return; }}... }Copy the code
  • Where currentGenerator in while is ResourceCacheGenerator, DataCacheGenerator, SourceGenerator corresponding to the above states; The first two are cached let’s look at startNext for SourceGenerator;

SourceGenerator.startNext

  • RunGenerators call is the most crucial step in the currentGenerator. StartNext
public boolean startNext() { ... while (! started && hasNextModelLoader()) { loadData = helper.getLoadData().get(loadDataListIndex++); if (loadData ! = null && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource()) || helper.hasLoadPath(loadData.fetcher.getDataClass()))) { started = true; startNextLoad(loadData); } } return started; } private void startNextLoad(final LoadData<? > toStart) {/ / here only need to pay close attention to the loadData. Fetcher. LoadData loadData method. The fetcher. LoadData (helper, getPriority (), 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); }}}); }Copy the code
  • The helper.getLoadData code in the above code is as follows
List<LoadData<? >> getLoadData() { if (! isLoadDataSet) { isLoadDataSet = true; loadData.clear(); List<ModelLoader<Object, ? >> modelLoaders = glideContext.getRegistry().getModelLoaders(model); //noinspection ForLoopReplaceableByForEach to improve perf for (int i = 0, size = modelLoaders.size(); i < size; i++) { ModelLoader<Object, ? > modelLoader = modelLoaders.get(i); LoadData<? > current = modelLoader.buildLoadData(model, width, height, options); if (current ! = null) { loadData.add(current); } } } return loadData; }Copy the code
  • Where buildLoadData is implemented in HttpGlideUrlLoader

HttpGlideUrlLoader.buildLoadData

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 = modelCache.get(model, 0, 0); if (url == null) { modelCache.put(model, 0, 0, model); url = model; } } int timeout = options.get(TIMEOUT); return new LoadData<>(url, new HttpUrlFetcher(url, timeout)); }Copy the code
  • And the SourceGenerator. StartNext startNextLoad, calls the loadData. Fetcher. LoadData method, and the last line from the code above we know the actual type is HttpUrlFetcher fetcher, So let’s look at the implementation of the loadData method

HttpUrlFetcher.loadData

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)); }}}Copy the code
  • “LoadDataWithRedirects”
private InputStream loadDataWithRedirects( URL url, int redirects, URL lastUrl, Map<String, String> headers) throws HttpException { ... urlConnection = buildAndConfigureConnection(url, headers); try { // Connect explicitly to avoid errors in decoders if connection fails. urlConnection.connect(); // Set the stream so that it's closed in cleanup to avoid resource leaks. See #2352. stream = urlConnection.getInputStream(); } catch (IOException e) { throw new HttpException( "Failed to connect or obtain data", getHttpStatusCodeOrInvalid(urlConnection), e); } if (isCancelled) { return null; } final int statusCode = getHttpStatusCodeOrInvalid(urlConnection); . }Copy the code
  • In the code above we see the familiar urlConnection, then see buildAndConfigureConnection method implementation
private HttpURLConnection buildAndConfigureConnection(URL url, Map<String, String> headers) throws HttpException { HttpURLConnection urlConnection; try { urlConnection = connectionFactory.build(url); } catch (IOException e) { throw new HttpException("URL.openConnection threw", /*statusCode=*/ 0, e); } for (Map.Entry<String, String> headerEntry : headers.entrySet()) { urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue()); } urlConnection.setConnectTimeout(timeout); urlConnection.setReadTimeout(timeout); urlConnection.setUseCaches(false); urlConnection.setDoInput(true); // Stop the urlConnection instance of HttpUrlConnection from following redirects so that // redirects will be handled by  recursive calls to this method, loadDataWithRedirects. urlConnection.setInstanceFollowRedirects(false); return urlConnection; }Copy the code
  • It is an HttpURLConnection that Glide used to make network requests

SourceGenerator.onDataReadyInternal

  • And from above the SourceGenerator. The loadData startNextNext. Fetcher. LoadData into DataCallback refs onDataReady we know, if the request is successful, The resource is passed back through onDataReady, which calls onDataReadyInternal, as shown below
void onDataReadyInternal(LoadData<? > loadData, Object data) {/ / disk for loading strategy DiskCacheStrategy DiskCacheStrategy = helper. GetDiskCacheStrategy (); If (data! = null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) { dataToCache = data; // We might be being called back on someone else's thread. Before doing anything, we should // reschedule to get back onto Glide's thread. cb.reschedule(); } else {DecodeJob cb.onDataFetcherReady(loadData.sourcekey, data, loadData.fetcher, DecodeJob cb.onDataFetcherReady, loadData.sourcekey, data, loadData.fetcher, loadData.fetcher.getDataSource(), originalKey); }}Copy the code
  • The cb for DecodeJob actually, so take a look at DecodeJob. OnDataFetcherReady
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; this.isLoadingFromAlternateCacheKey = sourceKey ! = decodeHelper.getCacheKeys().get(0); if (Thread.currentThread() ! = currentThread) { runReason = RunReason.DECODE_DATA; callback.reschedule(this); } else { GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData"); try { decodeFromRetrievedData(); } finally { GlideTrace.endSection(); }}}Copy the code
  • DecodeFromRetrievedData is called again in the above code as follows
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) {/ / inform pictures get notifyEncodeAndRelease (resource, currentDataSource isLoadingFromAlternateCacheKey); } else { runGenerators(); }}Copy the code
  • The notifyEncodeAndRelease code is as follows
private void notifyEncodeAndRelease( Resource<R> resource, DataSource dataSource, boolean isLoadedFromAlternateCacheKey) { if (resource instanceof Initializable) { ((Initializable) resource).initialize(); } Resource<R> result = resource; LockedResource<R> lockedResource = null; if (deferredEncodeManager.hasResourceToEncode()) { lockedResource = LockedResource.obtain(resource); result = lockedResource; } notifyComplete(result, dataSource, isLoadedFromAlternateCacheKey); stage = Stage.ENCODE; try { if (deferredEncodeManager.hasResourceToEncode()) { deferredEncodeManager.encode(diskCacheProvider, options); } } finally { if (lockedResource ! = null) { lockedResource.unlock(); } } // Call onEncodeComplete outside the finally block so that it's not called if the encode process // throws. onEncodeComplete(); }Copy the code

reference

  • Glide source
  • Glide official Chinese document
  • Android picture loading framework the most complete analysis (two), from the perspective of the source understanding Glide’s execution process
  • Glide principle in the history of the most complete
  • Glide source analysis (five) cache strategy, load strategy

My name is Jinyang. If you want to learn more about jinyang, please pay attention to the wechat public number “Jinyang said” to receive my latest articles