In the last article in this series, I took you through the source code for Glide and got a first look at the basic execution flow of this powerful image loading framework.

However, the last article can only be said to be a relatively rough reading of the entire implementation process of Glide source code, understand the basic working principle of Glide, but did not go to in-depth analysis of every detail (in fact, it is not possible in an article in-depth analysis of every source details). So from the beginning of this article, we will one by one for Glide a function of in-depth analysis, slowly Glide in the function of a comprehensive grasp.

Let’s start with caching today. But today the source code in the article are built on the basis of the last source code analysis, has not seen the last article friends, suggested to read the Android picture loading framework the most complete analysis (two), from the perspective of the source code to understand Glide’s execution process.

Glide Cache Introduction

Glide’s cache design can be said to be very advanced and well considered. For caching, Glide splits it into two modules, one memory cache and one hard disk cache.

The functions of the two cache modules are different. The main function of the memory cache is to prevent the application from repeatedly reading image data into the memory, while the main function of the disk cache is to prevent the application from repeatedly downloading and reading data from the network or other places.

It is the combination of memory cache and hard disk cache that makes Glide excellent picture cache effect, so next we will respectively analyze the use of these two kinds of cache and their implementation principle.

The cache Key

Since it is a caching function, there must be a Key for caching. So how is Glide’s cache Key generated? I have to say that Glide’s cache Key generation rules are very cumbersome, with as many as 10 parameters determining the cache Key. But cumbersome cumbersome, at least the logic is relatively simple, we first look at the Glide cache Key generation logic.

The code that generates the cache Key is in the Load () method of the Engine class, which we examined in the previous article but ignored, so let’s revisit:

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(); EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(), loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(), transcoder, loadProvider.getSourceEncoder()); . }... }Copy the code

As you can see here, the fetcher.getid () method is called at line 11 to get an ID string, which is the unique identifier of the image we want to load, such as the URL for an image on the web.

Then, in line 12, we pass this ID into the EngineKeyFactory buildKey() method, along with signature, width, height, and 10 other arguments, creating a EngineKey object. The EngineKey is the same as the cache Key in Glide.

If you change the width or height of the image using override(), a completely different cache Key will be generated.

The equals() and hashCode() methods are overridden to ensure that the EngineKey is treated as the same object only if all the parameters passed to it are the same. I won’t post the source code here.

Memory cache

Now that we have the cache Key, we can start caching, so let’s start with the in-memory cache.

The first thing you need to know is that Glide is automatically turned on by default. That is, when we use Glide to load an image, the image will be cached in memory, as long as it is not cleared from memory, the next time we use Glide to load the image will be read directly from memory, not from the network or hard disk again. This will undoubtedly improve the efficiency of image loading. For example, you repeatedly slide up and down in a RecyclerView, RecyclerView as long as it is Glide loaded pictures can be directly read from the memory quickly and display, thus greatly improving the user experience.

The most personal thing about Glide is that you don’t even need to write any extra code to get this convenient memory caching function automatically, because Glide already has it on by default.

So now that this feature is turned on by default, what else is there to talk about? Just one thing: If you need to disable memory caching for any particular reason, Glide provides an interface for this:

Glide.with(this)
     .load(url)
     .skipMemoryCache(true)
     .into(imageView);
Copy the code

As you can see, Glide’s memory caching is disabled simply by calling the skipMemoryCache() method and passing true.

Yes, Glide memory cache usage is only so much, can be said to be quite simple. But we can not only stay in such a simple level, let us read the source code to analyze Glide memory cache function is how to achieve.

In fact, when it comes to the implementation of memory cache, it is very easy to think of LruCache algorithm (Least Recently Used), also known as the Least Recently Used algorithm. Its main algorithm is to store the most recently used objects in the LinkedHashMap with strong references, and remove the least recently used objects from memory before the cache value reaches the preset value. LruCache usage is also relatively simple, I in Android efficient load large, multi-map solution, effective avoid program OOM this article has mentioned its usage, interested friends can go to reference.

So needless to say, Glide memory cache is the natural implementation of LruCache algorithm. But in addition to the LruCache algorithm, Glide also combined with a weak reference mechanism, to complete the memory cache function, let us come to the source code analysis.

First recall, in an article in the second step load () method, we analyze the in loadGeneric () method will be called Glide. BuildStreamModelLoader () method to get a ModelLoader object. At that time did not follow up to this method inside to analyze again, so we now look at its source:

public class Glide { public static <T, Y> ModelLoader<T, Y> buildModelLoader(Class<T> modelClass, Class<Y> resourceClass, Context context) { if (modelClass == null) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Unable to load null model, setting placeholder only"); } return null; } return Glide.get(context).getLoaderFactory().buildModelLoader(modelClass, resourceClass); } public static Glide get(Context context) { if (glide == null) { synchronized (Glide.class) { if (glide == null) { Context applicationContext = context.getApplicationContext(); List<GlideModule> modules = new ManifestParser(applicationContext).parse(); GlideBuilder builder = new GlideBuilder(applicationContext); for (GlideModule module : modules) { module.applyOptions(applicationContext, builder); } glide = builder.createGlide(); for (GlideModule module : modules) { module.registerComponents(applicationContext, glide); } } } } return glide; }... }Copy the code

Let’s just focus on the key here. When we build the ModelLoader object in line 11, we call a Glide.get() method, which is the key. Glide object is created by calling GlideBuilder’s createGlide() method on line 24, so let’s go with this method:

public class GlideBuilder { ... Glide createGlide() { if (sourceService == null) { final int cores = Math.max(1, Runtime.getRuntime().availableProcessors()); sourceService = new FifoPriorityThreadPoolExecutor(cores); } if (diskCacheService == null) { diskCacheService = new FifoPriorityThreadPoolExecutor(1); } MemorySizeCalculator calculator = new MemorySizeCalculator(context); if (bitmapPool == null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { int size = calculator.getBitmapPoolSize(); bitmapPool = new LruBitmapPool(size); } else { bitmapPool = new BitmapPoolAdapter(); } } if (memoryCache == null) { memoryCache = new LruResourceCache(calculator.getMemoryCacheSize()); } if (diskCacheFactory == null) { diskCacheFactory = new InternalCacheDiskCacheFactory(context); } if (engine == null) { engine = new Engine(memoryCache, diskCacheFactory, diskCacheService, sourceService); } if (decodeFormat == null) { decodeFormat = DecodeFormat.DEFAULT; } return new Glide(engine, memoryCache, bitmapPool, context, decodeFormat); }}Copy the code

This is where you build Glide objects. So if you look at line 22, you’ll see that a new LruResourceCache is created and assigned to the memoryCache object. It is the LruCache object used by Glide to implement memory caching. However, I am not going to expand here to talk about the specific implementation of LruCache algorithm, if you are interested in it can study its source code.

Now that the LruResourceCache object has been created, let’s take a step by step look at how memory caching is implemented in Glide.

The load() method used in Engine generates a cache Key. The load() method used in Engine generates a cache Key. The load() method used in Engine generates a cache Key.

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(); EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(), loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(), transcoder, loadProvider.getSourceEncoder()); EngineResource<? > 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; } EngineResource<? > 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 new LoadStatus(cb, engineJob); }... }Copy the code

As you can see, the loadFromCache() method is called here at line 17 to get the cached image, and if it does, the cb.onResourceready () method is called directly for the callback. If not, the loadFromActiveResources() method is called at line 26 to retrieve the cached image, which is also called back directly. Only if neither method retrieves the cache does it proceed, opening the thread to load the image.

That is, Glide’s image load calls two methods to get the memory cache, loadFromCache() and loadFromActiveResources(). One of these two methods uses the LruCache algorithm and the other uses weak references. Let’s take a look at their source code:

public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener { private final MemoryCache cache; private final Map<Key, WeakReference<EngineResource<? >>> activeResources; . private EngineResource<? > loadFromCache(Key key, boolean isMemoryCacheable) { if (! isMemoryCacheable) { return null; } EngineResource<? > cached = getEngineResourceFromCache(key); if (cached ! = null) { cached.acquire(); activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue())); } return cached; } private EngineResource<? > getEngineResourceFromCache(Key key) { Resource<? > cached = cache.remove(key); final EngineResource result; if (cached == null) { result = null; } else if (cached instanceof EngineResource) { result = (EngineResource) cached; } else { result = new EngineResource(cached, true ); } return result; } private EngineResource<? > loadFromActiveResources(Key key, boolean isMemoryCacheable) { if (! isMemoryCacheable) { return null; } EngineResource<? > active = null; WeakReference<EngineResource<? >> activeRef = activeResources.get(key); if (activeRef ! = null) { active = activeRef.get(); if (active ! = null) { active.acquire(); } else { activeResources.remove(key); } } return active; }... }Copy the code

At the beginning of the loadFromCache() method, isMemoryCacheable is checked to see if it is false, and null is returned if it is. What does that mean? We just learned a skipMemoryCache() method. If true is passed in this method, isMemoryCacheable here will be false, indicating that memory caching is disabled.

We continue to stay, then call the getEngineResourceFromCache () method to get the cache. The cache object is LruResourceCache, which is the LruResourceCache algorithm that was created when Glide was built.

However, look at line 22, when we get the cached image from LruResourceCache we remove it from the cache, and then store the cached image into activeResources at line 16. ActiveResources is a weakly referenced HashMap that caches images in use. As we can see, the loadFromActiveResources() method is taken from the HashMap activeResources. Using activeResources to cache images in use protects them from being recycled by the LruCache algorithm.

Ok, so that’s about the logic of reading data from an in-memory cache. In summary, if the image to be loaded can be read from the memory cache, then the callback is directly performed, if not, then the thread is started to perform the following image loading logic.

Now that we understand how the memory cache reads, the next question is where is the memory cache written? Here we go back to the last article. Remember that when the image is loaded, a message is sent to the EngineJob by the Handler to cut the execution logic back to the main thread and execute the handleResultOnMainThread() method. So let’s revisit the method as follows:

class EngineJob implements EngineRunnable.EngineRunnableManager { private final EngineResourceFactory engineResourceFactory; . private void handleResultOnMainThread() { if (isCancelled) { resource.recycle(); return; } else if (cbs.isEmpty()) { throw new IllegalStateException("Received a resource without any callbacks to notify"); } engineResource = engineResourceFactory.build(resource, isCacheable); hasResource = true; engineResource.acquire(); listener.onEngineJobComplete(key, engineResource); for (ResourceCallback cb : cbs) { if (! isInIgnoredCallbacks(cb)) { engineResource.acquire(); cb.onResourceReady(engineResource); } } engineResource.release(); } static class EngineResourceFactory { public <R> EngineResource<R> build(Resource<R> resource, boolean isMemoryCacheable) { return new EngineResource<R>(resource, isMemoryCacheable); }}... }Copy the code

At line 13, an EngineResource object containing the image resource is built using the EngineResourceFactory, This object is then called back to Engine’s onEngineJobComplete() method on line 16, as shown below:

public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener { ... @Override public void onEngineJobComplete(Key key, EngineResource<? > resource) { Util.assertMainThread(); if (resource ! = null) { resource.setResourceListener(key, this); if (resource.isCacheable()) { activeResources.put(key, new ResourceWeakReference(key, resource, getReferenceQueue())); } } jobs.remove(key); }... }Copy the code

It should be obvious now that at line 13, the EngineResource that was called back is put into activeResources, the cache that was written here.

So this is just a weak reference cache, there’s another kind of LruCache cache where is it written? This brings us to a reference mechanism in EngineResource. Looking at the handleResultOnMainThread() method, the acquire() method on EngineResource is called at line 15 and 19, and its Release () method is called at line 23. An acquired variable is used to record the number of times an image is referenced. Acquire () will increment the variable and release() will decrease the variable by 1, as shown below:

class EngineResource<Z> implements Resource<Z> {

    private int acquired;
    ...

    void acquire() {
        if (isRecycled) {
            throw new IllegalStateException("Cannot acquire a recycled resource");
        }
        if (!Looper.getMainLooper().equals(Looper.myLooper())) {
            throw new IllegalThreadStateException("Must call acquire on the main thread");
        }
        ++acquired;
    }

    void release() {
        if (acquired <= 0) {
            throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
        }
        if (!Looper.getMainLooper().equals(Looper.myLooper())) {
            throw new IllegalThreadStateException("Must call release on the main thread");
        }
        if (--acquired == 0) {
            listener.onResourceReleased(key, this);
        }
    }
}
Copy the code

That is, when the acquired variable is greater than 0, the image is in use and should be placed in the Activity Resource weak reference cache. After release(), if the acquired variable is equal to 0, the picture will no longer be used. The onResourceReleased() method of the listener will be called in line 24 to release the resource. The listener will call the onResourceReleased() method:

public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener { private final MemoryCache cache; private final Map<Key, WeakReference<EngineResource<? >>> activeResources; . @Override public void onResourceReleased(Key cacheKey, EngineResource resource) { Util.assertMainThread(); activeResources.remove(cacheKey); if (resource.isCacheable()) { cache.put(cacheKey, resource); } else { resourceRecycler.recycle(resource); }}... }Copy the code

As you can see, the cached image is first removed from activeResources and then put into LruResourceCache. In this way, images in use are cached using weak references and images not in use are cached using LruCache.

This is how Glide memory cache is implemented.

Hard disk cache

Let’s start with hard disk caching.

If you remember, we used hard disk caching in the first article of this series. The following code was used to disable Glide’s disk caching of images:

Glide.with(this)
     .load(url)
     .diskCacheStrategy(DiskCacheStrategy.NONE)
     .into(imageView);
Copy the code

To disable disk caching, call diskCacheStrategy() and pass diskCacheStrategy.None.

The diskCacheStrategy() method, which is basically everything about the Glide cache function, can accept four arguments:

  • Diskcachestrategy. NONE: indicates that no content is cached.
  • Diskcachestrategy. SOURCE: indicates that only raw images are cached.
  • Diskcachestrategy. RESULT: Only converted images are cached (default).
  • Diskcachestrategy. ALL: caching both the original image and the converted image.

The interpretation of the above four parameters in itself is not hard to understand, but there is a concept we need to know, is when we use the Glide to load an image, Glide default would not original pictures show come out, but to compress images and transformation (we will study this aspect of the content at the back). In short, after a series of operations to get the picture, is called after the conversion of the picture. Glide, by default, cached the transformed image on disk, and we can change this behavior by calling diskCacheStrategy().

Well, the use of Glide cache is only so much, so the next or the old routine, we read the source code to analyze, Glide’s hard disk cache function is how to achieve.

First, like memory caching, hard disk caching is implemented using the LruCache algorithm, and Google also provides an off-the-shelf utility class DiskLruCache. I have also written a special article on the DiskLruCache tool for a more comprehensive analysis, interested friends can refer to the Android DiskLruCache full resolution, the best solution for hard disk caching. Glide, of course, uses a self-written DiskLruCache tool class, but the basic implementation principles are the same.

Let’s take a look at where Glide reads the disk cache. Glide starts the thread to load the image and executes EngineRunnable’s Run () method, which in turn calls a decode() method, So let’s look at the source code of the decode() method again:

private Resource<?> decode() throws Exception {
    if (isDecodingFromCache()) {
        return decodeFromCache();
    } else {
        return decodeFromSource();
    }
}
Copy the code

As you can see, there are two cases, one is to call decodeFromCache() to read the image from the hard disk cache, and the other is to call decodeFromSource() to read the original image. By default, Glide will read first from the cache and only read the original image if there are no images in the cache to read. DecodeFromCache (); decodeFromCache(); decodeFromCache();

private Resource<? > decodeFromCache() throws Exception { Resource<? > result = null; try { result = decodeJob.decodeResultFromCache(); } catch (Exception e) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Exception decoding result from cache: " + e); } } if (result == null) { result = decodeJob.decodeSourceFromCache(); } return result; }Copy the code

DecodeJob decodeResultFromCache() to get cache. If not, call decodeSourceFromCache() to get cache. The difference between these two methods is the difference between diskCacheStrategy. RESULT and diskCacheStrategy. SOURCE.

So let’s take a look at the source code for these two methods, as follows:

public Resource<Z> decodeResultFromCache() throws Exception { if (! diskCacheStrategy.cacheResult()) { return null; } long startTime = LogTime.getLogTime(); Resource<T> transformed = loadFromCache(resultKey); startTime = LogTime.getLogTime(); Resource<Z> result = transcode(transformed); return result; } public Resource<Z> decodeSourceFromCache() throws Exception { if (! diskCacheStrategy.cacheSource()) { return null; } long startTime = LogTime.getLogTime(); Resource<T> decoded = loadFromCache(resultKey.getOriginalKey()); return transformEncodeAndTranscode(decoded); }Copy the code

DecodeResultFromCache () decodeResultFromCache() decodeResultFromCache() decodeResultFromCache() Also called the transformEncodeAndTranscode () method first converts data decoding and return again.

However, we notice that the loadFromCache() method is called with different parameters. One calls the resultKey method, while the other calls the resultKey getOriginalKey() method. Glide’s cache Key is made up of 10 parameters, including width, height, and so on. But if we were caching the raw image, we wouldn’t need so many parameters because we wouldn’t have to change the image at all. GetOriginalKey () = getOriginalKey();

public Key getOriginalKey() {
    if (originalKey == null) {
        originalKey = new OriginalKey(id, signature);
    }
    return originalKey;
}
Copy the code

As you can see, most of the parameters are ignored and only id and signature are used to form the cache Key. The signature parameter is not used in most cases, so it is basically the Original cache Key determined by the ID (i.e. image URL).

LoadFromCache () {loadFromCache(); loadFromCache();

private Resource<T> loadFromCache(Key key) throws IOException {
    File cacheFile = diskCacheProvider.getDiskCache().get(key);
    if (cacheFile == null) {
        return null;
    }
    Resource<T> result = null;
    try {
        result = loadProvider.getCacheDecoder().decode(cacheFile, width, height);
    } finally {
        if (result == null) {
            diskCacheProvider.getDiskCache().delete(key);
        }
    }
    return result;
}
Copy the code

The logic of this method is very simple, call getDiskCache() method to get is Glide written by its own DiskLruCache tool class instance, and then call its get() method and the cache Key passed in, you can get the disk cache file. Return NULL if the file is empty, or decode it as a Resource object if it is not.

So we will read the hard disk cache source analysis is finished, so where is the hard disk cache written? Strike while the iron is hot Let’s get on with our analysis.

As analyzed earlier, in the absence of caching, the decodeFromSource() method is called to read the raw image. So let’s look at this method:

public Resource<Z> decodeFromSource() throws Exception {
    Resource<T> decoded = decodeSource();
    return transformEncodeAndTranscode(decoded);
}
Copy the code

This method is only two lines of code, decodeSource () is used to resolve the original image as the name implies, the transformEncodeAndTranscode () is used to convert image and transcoding. Let’s look at decodeSource() first:

private Resource<T> decodeSource() throws Exception {
    Resource<T> decoded = null;
    try {
        long startTime = LogTime.getLogTime();
        final A data = fetcher.loadData(priority);
        if (isCancelled) {
            return null;
        }
        decoded = decodeFromSourceData(data);
    } finally {
        fetcher.cleanup();
    }
    return decoded;
}

private Resource<T> decodeFromSourceData(A data) throws IOException {
    final Resource<T> decoded;
    if (diskCacheStrategy.cacheSource()) {
        decoded = cacheAndDecodeSourceData(data);
    } else {
        long startTime = LogTime.getLogTime();
        decoded = loadProvider.getSourceDecoder().decode(data, width, height);
    }
    return decoded;
}

private Resource<T> cacheAndDecodeSourceData(A data) throws IOException {
    long startTime = LogTime.getLogTime();
    SourceWriter<A> writer = new SourceWriter<A>(loadProvider.getSourceEncoder(), data);
    diskCacheProvider.getDiskCache().put(resultKey.getOriginalKey(), writer);
    startTime = LogTime.getLogTime();
    Resource<T> result = loadFromCache(resultKey.getOriginalKey());
    return result;
}
Copy the code

The loadData() method of fetcher is called at line 5 to read the image data, and the decodeFromSourceData() method is called at line 9 to decode the image. Next, at line 18, we determine whether caching the raw image is allowed, and if so, we call the cacheAndDecodeSourceData() method. This method also calls getDiskCache() to get the DiskLruCache instance, and then calls its PUT () method to write to the disk cache. Note that the original image cache Key is resultKey.getoriginalKey ().

Ok, original image cache writes is so simple, we analyze the transformEncodeAndTranscode () method of source code, to see the pictures after the conversion is how to write to the cache. The code looks like this:

private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) { long startTime = LogTime.getLogTime(); Resource<T> transformed = transform(decoded); writeTransformedToCache(transformed); startTime = LogTime.getLogTime(); Resource<Z> result = transcode(transformed); return result; } private void writeTransformedToCache(Resource<T> transformed) { if (transformed == null || ! diskCacheStrategy.cacheResult()) { return; } long startTime = LogTime.getLogTime(); SourceWriter<Resource<T>> writer = new SourceWriter<Resource<T>>(loadProvider.getEncoder(), transformed); diskCacheProvider.getDiskCache().put(resultKey, writer); }Copy the code

The logic here is a little bit more straightforward. The transform() method is called at line 3 to convert the image, and then the writeTransformedToCache() method is used to write the transformed image to the hard disk cache, again using the Put () method of the DiskLruCache instance. But the cache Key used here is a resultKey.

So we will Glide hard disk cache implementation principle also finished analysis. Although the source code may seem complex, Glide’s excellent packaging makes it easy to control The caching capabilities of Glide using only skipMemoryCache() and diskCacheStrategy().

After understanding the principle of Glide cache implementation, let’s learn some advanced skills of Glide cache.

Although Glide’s highly encapsulated caching makes usage very simple, it also presents some problems.

For example, a friend in the group told me that the picture resources of their project are stored on Qiuniuyun. In order to protect the picture resources, Qiuniuyun will add a token parameter on the basis of the picture URL address. That is, an image url might look like this:

http://url.com/image.jpg?token=d9caa6e02c990b0a
Copy the code

Glide loads the image, and this URL is used as the cache Key.

But then there is the problem that token as a parameter to verify identity is not static and may change from moment to moment. If the token changes, the image URL changes, and the cache Key changes. As a result, it is clearly the same picture, but because the token keeps changing, the cache function of Glide is completely invalid.

This is actually a very difficult problem, and I believe that it is definitely not only a case of Seven Niuyun, everyone is likely to encounter this problem when using Glide.

So how to solve this problem? We are still from the source of the level of analysis, first look at the Glide generation cache Key this part of the code:

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(); EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(), loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(), transcoder, loadProvider.getSourceEncoder()); . }... }Copy the code

If you look at line 11, as I said earlier, this ID is actually the URL of the image. Fetcher () : HttpUrlFetcher () : HttpUrlFetcher () : getId() : HttpUrlFetcher () : getId() : HttpUrlFetcher () : getId() : HttpUrlFetcher () : getId();

public class HttpUrlFetcher implements DataFetcher<InputStream> { private final GlideUrl glideUrl; . public HttpUrlFetcher(GlideUrl glideUrl) { this(glideUrl, DEFAULT_CONNECTION_FACTORY); } HttpUrlFetcher(GlideUrl glideUrl, HttpUrlConnectionFactory connectionFactory) { this.glideUrl = glideUrl; this.connectionFactory = connectionFactory; } @Override public String getId() { return glideUrl.getCacheKey(); }... }Copy the code

As you can see, the getId() method calls the getCacheKey() method of GlideUrl. So where does this GlideUrl object come from? It’s the image URL we passed in the load() method, and Glide wraps that URL internally into a GlideUrl object.

GlideUrl getCacheKey() : getCacheKey() : getCacheKey();

public class GlideUrl { private final URL url; private final String stringUrl; . public GlideUrl(URL url) { this(url, Headers.DEFAULT); } public GlideUrl(String url) { this(url, Headers.DEFAULT); } public GlideUrl(URL url, Headers headers) { ... this.url = url; stringUrl = null; } public GlideUrl(String url, Headers headers) { ... this.stringUrl = url; this.url = null; } public String getCacheKey() { return stringUrl ! = null ? stringUrl : url.toString(); }... }Copy the code

I’ve simplified the code a little bit to make it a little bit more straightforward. The constructor of the GlideUrl class takes two types of arguments, a URL string and a URL object. Then the logic in the getCacheKey() method is very simple: if a URL string is passed in, the string itself is returned, and if a URL object is passed in, the result of the object toString() is returned.

In fact, I’m sure you’ve already guessed the solution, because the logic in getCacheKey() is so straightforward that it simply returns the image URL as the cache Key. All we need to do is rewrite the getCacheKey() method and add some logic of our own to solve the problem.

Create a MyGlideUrl inherited from GlideUrl as follows:

public class MyGlideUrl extends GlideUrl { private String mUrl; public MyGlideUrl(String url) { super(url); mUrl = url; } @Override public String getCacheKey() { return mUrl.replace(findTokenParam(), ""); } private String findTokenParam() { String tokenParam = ""; int tokenKeyIndex = mUrl.indexOf("? token=") >= 0 ? mUrl.indexOf("? token=") : mUrl.indexOf("&token="); if (tokenKeyIndex ! = -1) { int nextAndIndex = mUrl.indexOf("&", tokenKeyIndex + 1); if (nextAndIndex ! = -1) { tokenParam = mUrl.substring(tokenKeyIndex + 1, nextAndIndex + 1); } else { tokenParam = mUrl.substring(tokenKeyIndex); } } return tokenParam; }}Copy the code

As you can see, we’ve overridden the getCacheKey() method to add logic to remove the token argument from the image URL. The result of getCacheKey() is a URL with no token arguments, so that Glide’s cache Key is fixed no matter how the token changes.

Of course, once MyGlideUrl is defined, we still need to use it. Change the code to load the image as follows:

Glide.with(this)
     .load(new MyGlideUrl(url))
     .into(imageView);
Copy the code

In other words, we need to pass the custom MyGlideUrl object in the load() method instead of passing the URL string directly as before. Glide would have used the original GlideUrl class internally instead of our custom MyGlideUrl class.

So we’ve solved this tricky caching problem.

Ok, Glide cache on the content of today’s analysis here, now we not only master the basic use of Glide cache and advanced skills, but also understand the implementation principle behind it, and harvest a full article ah. In the next article, I will continue to take you to in-depth analysis of Glide’s other functional modules, talk about the knowledge of callback, interested friends please continue to read Android picture loading framework the most complete analysis (four), play Glide callback and monitoring.

Pay attention to my technical public account “Guo Lin”, there are high-quality technical articles pushed every day. Pay attention to my entertainment public number, work, study tired when relax yourself.