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

preface

Glide memory cache has two levels: LruCache and ActiveResources

LruCache is a platitude, so I won’t go into details here.

ActiveResources actually contains a HashMap in which values are weak references to resources.

So how do these two levels work?

Take out the

First from LruCache, not from ActiveResources

If there is one in LruCache, it is removed and stored in ActiveResources and removed from LruCache

The code is as follows:

public class Engine implements EngineJobListener.MemoryCache.ResourceRemovedListener.EngineResource.ResourceListener {...public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
            DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
            Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
        Util.assertMainThread();
        long startTime = LogTime.getLogTime();

        final String id = fetcher.getId();
        // Generate a cache key
        EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
                loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
                transcoder, loadProvider.getSourceEncoder());
        // Get the cached image from LruCacheEngineResource<? > cached = loadFromCache(key, isMemoryCacheable);if(cached ! =null) {
            cb.onResourceReady(cached);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Loaded resource from cache", startTime, key);
            }
            return null;
        }
        // Get the image from the weak referenceEngineResource<? > active = loadFromActiveResources(key, isMemoryCacheable);if(active ! =null) {
            cb.onResourceReady(active);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Loaded resource from active resources", startTime, key);
            }
            return null;
        }

        EngineJob current = jobs.get(key);
        if(current ! =null) {
            current.addCallback(cb);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Added to existing load", startTime, key);
            }
            return new LoadStatus(cb, current);
        }

        EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
        DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
                transcoder, diskCacheProvider, diskCacheStrategy, priority);
        EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
        jobs.put(key, engineJob);
        engineJob.addCallback(cb);
        engineJob.start(runnable);

        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Started new load", startTime, key);
        }
        return newLoadStatus(cb, engineJob); }... }Copy the code

storage

If the memory is not available locally, it is retrieved from the network and stored in ActiveResources. The ActiveResources stores weak references to EngineResource objects.

EngineResource is a class that encapsulates resources. It has an acquired count, which records the number of times the resource is referenced. The Acquired function is +1 when the resource is taken out and used, and the release function is -1 when the resource is released. When acquired is 0, it is removed from ActiveResources and stored in LruCache.

The code is as follows:

void release(a) {
  synchronized (listener) {
    synchronized (this) {
      if (acquired <= 0) {
        throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
      }
      if (--acquired == 0) {
        listener.onResourceReleased(key, this); }}}}Copy the code

A listener is an Engine object

@Override
public synchronized void onResourceReleased(Key cacheKey, EngineResource
        resource) {
  activeResources.deactivate(cacheKey);
  if (resource.isCacheable()) {
    cache.put(cacheKey, resource);
  } else{ resourceRecycler.recycle(resource); }}Copy the code

You can see that if memory caching is enabled, LruCache is stored, otherwise it is released directly.

Two levels of cache

This gives us an idea of what glide memory’s two-level cache is, which is actually a division of the cached resources: used and used.

Put ActiveResources into use, which can prevent being recycled by the LruCache algorithm; The used memory is stored in LruCache and the total memory is controlled by algorithm.

When release is executed

The Acquired function of EngineResource is called when the resource is being used and the Release function of EngineResource is called when the resource is being released.

It’s easy to understand when you use it, when you take it out, it’s actually when you use it, it’s an active action.

But when? How is resource release monitored in Glide?

Find it in Engine by looking for the release call to EngineResource

public void release(Resource
        resource) {
  if (resource instanceofEngineResource) { ((EngineResource<? >) resource).release(); }else {
    throw new IllegalArgumentException("Cannot release anything but an EngineResource"); }}Copy the code

Go ahead and find where this function is called and find it in SingleRequest

@Override
public synchronized void clear(a) {
  assertNotCallingCallbacks();
  stateVerifier.throwIfRecycled();
  if (status == Status.CLEARED) {
    return;
  }
  cancel();
  // Resource must be released before canNotifyStatusChanged is called.
  if(resource ! =null) {
    releaseResource(resource);
  }
  if (canNotifyCleared()) {
    target.onLoadCleared(getPlaceholderDrawable());
  }

  status = Status.CLEARED;
  
  if(toRelease ! =null) { engine.release(toRelease); }}Copy the code

Where is the clear function called? In the ViewTarget

@Synthetic void pauseMyRequest(a) {
  Request request = getRequest();
  // If the Request were cleared by the developer, it would be null here. The only way it's
  // present is if the developer hasn't previously cleared this Target.
  if(request ! =null) {
    isClearedByUs = true;
    request.clear();
    isClearedByUs = false; }}Copy the code

The ViewTarget encapsulates the ImageView to load the image, and the release of the resource must be associated with the View.

ViewTarget has a protected final T view; This is the ImageView to load the image, and in the ViewTarget you can see that attach listener is added to this view:

view.addOnAttachStateChangeListener(attachStateListener);
Copy the code

AttachStateListener = attachStateListener

attachStateListener = new OnAttachStateChangeListener() {
  @Override
  public void onViewAttachedToWindow(View v) {
    resumeMyRequest();
  }

  @Override
  public void onViewDetachedFromWindow(View v) { pauseMyRequest(); }};Copy the code

So it is very obvious, every load picture view can register a OnAttachStateChangeListener, when removing the view from the interface, that is, when the resources are no longer referenced, is called pauseMyRequest, The final reference count for EngineResource is -1.

This ensures that when a resource in ActiveResources is no longer referenced, the resource is moved to LruCache.