An article to get "Three-level cache of picture frame Glide (full)"

foreword

The content of Glide is too huge, and it is unbearable to put it together. Let's take out the third-level cache list and talk about it.
First of all, the third-level cache is a very important caching mechanism in Glide, and it is this caching mechanism that makes our image loading efficiency and performance so high.
Let's take a look at the specific three-level cache content

Reading from L3 cache

First of all, what does Glide's L3 cache contain? Different from the common three-level cache (memory, local, network)
Glide's three-level cache describes the following three levels: (Of course, there must be a network cache, but the three-level cache mentioned by Glide does not include the network cache)

Get the source of the cache (the first two levels, active cache and memory cache)

It happens in the Egine.load() method, which is the core method of Glide image loading. (If you don't know this method, you must have not read the previous article)
Egine.load() In addition to creating our very important EngineJob, DecodeJob is our caching strategy.

Oh, by the way, there is also an EngineKey generation (ps: the EngineKey entity constructed using multiple parameters, as the Key key of the HashMap in the cache)

public <R> Engine.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() : 0L;
    // 1、EngineKey 的生成
    EngineKey key = this.keyFactory.buildKey(model, signature, width, height, transformations, resourceClass, transcodeClass, options);
    EngineResource memoryResource;
    synchronized(this) {
    
    
        // 2、活动缓存和内存缓存的加载、获取(这里只是两级的)
        memoryResource = this.loadFromMemory(key, isMemoryCacheable, startTime);
        if (memoryResource == null) {
    
    
            // 3、EngineJob和DecodeJob的生成(第三级磁盘缓存在DecodeJob中)
            return this.waitForExistingOrStartNewJob(glideContext, model, signature, width, height, resourceClass, transcodeClass, priority, diskCacheStrategy, transformations, isTransformationRequired, isScaleOnlyOrNoTransform, options, isMemoryCacheable, useUnlimitedSourceExecutorPool, useAnimationPool, onlyRetrieveFromCache, cb, callbackExecutor, key, startTime);
        }
    }
    //回调 图片资源 (ps:当)
    cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE, false);
    return null;
}

loadFromMemory (here is only active cache and memory cache) (disk cache will be mentioned below)

@Nullable
private EngineResource<?> loadFromMemory(EngineKey key, boolean isMemoryCacheable, long startTime) {
    
    
    if (!isMemoryCacheable) {
    
    
        return null;
    } else {
    
    
        // 1、先从活动缓存中获取
        EngineResource<?> active = this.loadFromActiveResources(key);
        if (active != null) {
    
    
            if (VERBOSE_IS_LOGGABLE) {
    
    
                logWithTimeAndKey("Loaded resource from active resources", startTime, key);
            }

            return active;
        } else {
    
    
            // 2、从内存中获取
            EngineResource<?> cached = this.loadFromCache(key);
            if (cached != null) {
    
    
                if (VERBOSE_IS_LOGGABLE) {
    
    
                    logWithTimeAndKey("Loaded resource from cache", startTime, key);
                }

                return cached;
            } else {
    
    
                return null;
            }
        }
    }
}

Get the source of the cache (third level, disk cache)

As mentioned above, the third-level disk cache is in DecodeJob. Why talk about it separately? Because the acquisition of the disk cache is hidden deeper

if (memoryResource == null) {
    
    
    // 3、EngineJob和DecodeJob的生成(第三级磁盘缓存在DecodeJob中)
    return this.waitForExistingOrStartNewJob(glideContext, model, signature, width, height, resourceClass, transcodeClass, priority, diskCacheStrategy, transformations, isTransformationRequired, isScaleOnlyOrNoTransform, options, isMemoryCacheable, useUnlimitedSourceExecutorPool, useAnimationPool, onlyRetrieveFromCache, cb, callbackExecutor, key, startTime);
}

Let's take a look at it in detail:
the memoryResource has not been obtained by the first two levels of cache above. At this time, waitForExistingOrStartNewJob is called. In fact, everyone can guess that the disk cache is here. Here is a little more detail, don't dislike the code too much
waitForExistingOrStartNewJob

private <R> Engine.LoadStatus waitForExistingOrStartNewJob(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, EngineKey key, long startTime) {
    
    
        .....
        //获取缓的EngineJob
        .....
        //创建新的EngineJob、DecodeJob
        EngineJob<R> engineJob = this.engineJobFactory.build(key, isMemoryCacheable, useUnlimitedSourceExecutorPool, useAnimationPool, onlyRetrieveFromCache);
        DecodeJob<R> decodeJob = this.decodeJobFactory.build(glideContext, model, key, signature, width, height, resourceClass, transcodeClass, priority, diskCacheStrategy, transformations, isTransformationRequired, isScaleOnlyOrNoTransform, onlyRetrieveFromCache, options, engineJob);
        this.jobs.put(key, engineJob);
        engineJob.addCallback(cb, callbackExecutor);
        //加载任务的启动
        engineJob.start(decodeJob);
        if (VERBOSE_IS_LOGGABLE) {
    
    
            logWithTimeAndKey("Started new load", startTime, key);
        }

        return new Engine.LoadStatus(cb, engineJob);
    }
}

engineJob.start(decodeJob);
There is nothing to say, add the decodeJob task and execute it.

public synchronized void start(DecodeJob<R> decodeJob) {
    
    
    this.decodeJob = decodeJob;
    GlideExecutor executor = decodeJob.willDecodeFromCache() ? this.diskCacheExecutor : this.getActiveSourceExecutor();
    executor.execute(decodeJob);
}

Which execution thread will execute what? That must be run! ! ! Needless to say (simplify only look at disk cache)

public void run() {
    
    
    .....
    try {
    
    
        if (!this.isCancelled) {
    
    
            //主要方法
            this.runWrapped();
            return;
        }
    } catch (CallbackException var7) {
    
    


In the runWrapped() method, it represents the three stages of loading (the code is not carefully read, I will describe it directly)

  • Phase 1: In the initialization phase, call the getNextStage() method to get the next stage, call the getNextGenerator() method to get the next generator, and then call the runGenerators() method to execute the generator.
  • Phase 2: Switch to the source service phase and call the runGenerators() method to execute the generator.
  • Three phases: In the decoding data phase, the decodeFromRetrievedData() method is called to decode the obtained data.
private void runWrapped() {
    
    
    switch(this.runReason) {
    
    
    //1、一阶段
    case INITIALIZE:
        this.stage = this.getNextStage(DecodeJob.Stage.INITIALIZE);
        this.currentGenerator = this.getNextGenerator(); //关注点一
        this.runGenerators(); //关注点二
        break;
    //2、二阶段    
    case SWITCH_TO_SOURCE_SERVICE:
        this.runGenerators();
        break;
    //3、三阶段
    case DECODE_DATA:
        this.decodeFromRetrievedData();
        break;
    default:
        throw new IllegalStateException("Unrecognized run reason: " + this.runReason);
    }

}

It must be a stage for us to get pictures: the initialization stage, so we look at the focus one getNextGenerator()

private DataFetcherGenerator getNextGenerator() {
    
    
    switch(this.stage) {
    
    
    case RESOURCE_CACHE:
        //磁盘缓存中获取修改后的图片
        return new ResourceCacheGenerator(this.decodeHelper, this);
    case DATA_CACHE:
        //磁盘缓存中获取原始图片
        return new DataCacheGenerator(this.decodeHelper, this);
    case SOURCE:
        //直接网络请求图片
        return new SourceGenerator(this.decodeHelper, this);
    case FINISHED:
        return null;
    default:
        throw new IllegalStateException("Unrecognized stage: " + this.stage);
    }
}

Here we just create request objects in three different scenarios. Specific use, in (concern 2 runGenerators() method)

private void runGenerators() {
    
    
    ........
    while(!this.isCancelled && this.currentGenerator != null
     && !(isStarted = this.currentGenerator.startNext())) {
    
    
        .........
    }
    ......
}

Then execute currentGenerator.startNext()
ResourceCacheGenerator.startNext()

 public boolean startNext() {
    
    
    ...
    currentKey =
        new ResourceCacheKey
                helper.getArrayPool(),
    sourceId,
    helper.getSignature(),
    helper.getWidth(),
    helper.getHeight(),
    transformation,
    resourceClass,
    helper.getOptions()); //创建图片资源的key
    cacheFile = helper.getDiskCache().get(currentKey); //获取磁盘缓存
    ... //没有磁盘缓存,
    return started;
}

First, create a key for the current picture to identify the uniqueness of the picture, and then obviously, calling helper.getDiskCache().get(currentKey) is to get the picture resource from our disk cache, because here is the startNext() of ResourceCacheGenerator , so the obtained resource is a compressed or converted image.

How to get uncompressed and converted pictures? First of all, the DiskCacheStrategy type we set when setting the diskCacheStrategy() of RequestOptions can cache the original image (ALL, DATA, and AUTOMATIC can cache the original image), and then Glide can get the original image by calling the startNext() method of DataCacheGenerator. picture of.

Active Cache Details

  • Principle: weakly referenced HashMap
Map<Key, ActiveResources.ResourceWeakReference> activeEngineResources;
  • Scope: in the life cycle of the current Activity
  • Function: Share memory cache pressure and release memory in time.
  • Fetch priority: get from the active cache first
  • Proof of source code analysis
    Obtain EngineResource<?> loadFromActiveResources(Key key)
@Nullable
private EngineResource<?> loadFromActiveResources(Key key) {
    
    
    EngineResource<?> active = this.activeResources.get(key);
    if (active != null) {
    
    
        active.acquire();
    }

    return active;
}

@Nullable
synchronized EngineResource<?> get(Key key) {
    
    
    ActiveResources.ResourceWeakReference activeRef = 
    (ActiveResources.ResourceWeakReference)this.activeEngineResources.get(key);
    if (activeRef == null) {
    
    
        return null;
    } else {
    
    
        EngineResource<?> active = (EngineResource)activeRef.get();
        if (active == null) {
    
    
            this.cleanupActiveReference(activeRef);
        }

        return active;
    }
}

It can be seen that it is obtained from the weak reference Map. (Don’t worry about how to put it, I will talk about it later. Here I just talk about the concept of active cache)

Detailed memory cache

  • Principle: LruCache is managed by the LRU of LinkedHashMap, and it is packaged to provide some methods of the MemoryCache interface class, such as put and remove
public class LruCache<T, Y> {
    
    
    private final Map<T, LruCache.Entry<Y>> cache = new LinkedHashMap(100, 0.75F, true);
  • Scope: within the scope of a certain App, it does not exist if the application is completely exited
  • Role: speed up data reading, reduce disk IO operations and network requests
  • Take the priority: if there is no active cache, look for it in the memory cache
  • Source code parsing proves
    to obtain EngineResource<?> cached = this.loadFromCache(key);
private EngineResource<?> loadFromCache(Key key) {
    
    
    //1、步骤一:获取资源
    EngineResource<?> cached = this.getEngineResourceFromCache(key);
    if (cached != null) {
    
    
        //2、步骤二:图片使用次数计数
        cached.acquire();
        //3、步骤三:给活动缓存
        this.activeResources.activate(key, cached);
    }

    return cached;
}

Step 1: Obtaining resources getEngineResourceFromCache
is divided into the following two steps:

  • Get the image resource through the Key and remove it (this is the credit of LruCache management, we will handwrite the LRU algorithm later!!!)
  • wrap EngineResource and return
private EngineResource<?> getEngineResourceFromCache(Key key) {
    
    
    //1、通过Key获取图片资源,并移除
    Resource<?> cached = this.cache.remove(key);
    EngineResource result;
    if (cached == null) {
    
    
        result = null;
    } else if (cached instanceof EngineResource) {
    
    
        result = (EngineResource)cached;
    } else {
    
    
        result = new EngineResource(cached, true, true, key, this);
    }

    return result;
}

Step 2: Count the usage of images
Step 3: Package the image cache with weak references and put it in the Map of the active cache for management

synchronized void activate(Key key, EngineResource<?> resource) {
    
    
    ActiveResources.ResourceWeakReference toPut = new ActiveResources.ResourceWeakReference(key, resource, this.resourceReferenceQueue, this.isActiveResourceRetentionAllowed);
    ActiveResources.ResourceWeakReference removed = (ActiveResources.ResourceWeakReference)this.activeEngineResources.put(key, toPut);
    if (removed != null) {
    
    
        removed.reset();
    }
}

OK! The memory cache is also finished, and it also explains where the active cache is used, that is, in the memory cache, if it is used, the cache will be handed over to the active cache area for management (the function and design original intention of the active cache will be discussed later)

Detailed explanation of disk cache

  • Principle: DiskLruCache is managed by LRU of LinkedHashMap, and used by DiskCache interface and DiskLruCacheWrapper wrapper class.
public final class DiskLruCache implements Closeable {
    
    
    private final LinkedHashMap<String, Entry> lruEntries =
    new LinkedHashMap<String, Entry>(0, 0.75f, true);

The Key and Value in the LinkedHashMap of the LRU of the currently managed DiskLruCache are:

Key: Indicates the key of the cache entry, that is, the unique identifier of each cache entry (obtained from the URL).
Value: Indicates the specific content of the cache entry, which is an Entry object, which contains information such as the file path of the cache entry, cache size, and cache read-write lock.

  • Scope: the entire system, as long as the data is not deleted, it will always exist
  • Function: for permanent preservation
  • Fetching priority: If there is no memory cache, go to the disk cache to read
  • Source code proof: It has been explained in detail in the above acquisition cache source (the third level, disk cache)
public boolean startNext() {
    
     
  //1、磁盘缓存中获取
  cacheFile = helper.getDiskCache().get(currentKey);
  if (cacheFile != null) {
    
    
    sourceKey = sourceId;
    modelLoaders = helper.getModelLoaders(cacheFile);
    modelLoaderIndex = 0;
  }
}

while (!started && hasNextModelLoader()) {
    
    
  ...
  // 2、网络加载
  loadData = modelLoader.buildLoadData(
          cacheFile, helper.getWidth(), helper.getHeight(), helper.getOptions());
  ....
}

When the disk cache is not obtained, network loading is required.

Writing to L3 cache

Let's start by requesting an image from the network.

disk cache write

Because the image is not in the disk cache, the resulting network request returns the image:

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); //调用 SourceGenerator的onDataReady()方法
    }
    ...
}

Here, the picture is obtained from the network through the InputStream stream, and the callback is given to onDataReady
onDataReady does two things

  • Assign the dataToCache object image data, and call back cb.reschedule() to trigger the startNext() method of SourceGenerator to cache the original image
  • Call back directly to onDataFetcherReady (if no cache is required)
public void onDataReady(Object data) {
    
    
    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
    //如果请求返回的数据 data 不为空且需要缓存原始数据,就将 data 赋值给dataToCache,
    //接着调用 cb.reschedule() 会再一次进入到 SourceGenerator 的 startNext() 方法
    if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
    
    
        dataToCache = data; 
        cb.reschedule();
    } else {
    
    
        cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
            loadData.fetcher.getDataSource(), originalKey); 
    }
}

Next, if getNextGenerator() returns SourceGenerator when the disk is acquired, it indicates that it is necessary to request an image, and enter the startNext() method of SourceGenerator: (This source code is above to obtain the cache source (mentioned in the third level))

case SOURCE:
   //直接网络请求图片
  return new SourceGenerator(this.decodeHelper, this);
  
public boolean startNext() {
    
    
    if (dataToCache != null) {
    
    
        Object data = dataToCache;
        dataToCache = null;
        cacheData(data); //在这里
    }
    ...
}

So look at what cacheData does

  • Encoding (compression or conversion) image encoder
  • Write to disk cache (original image)
  • Create a DataCacheGenerator to write compressed and encoded pictures to the disk cache
private void cacheData(Object dataToCache) {
    
    
    long startTime = LogTime.getLogTime();
    try {
    
    
        Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
        DataCacheWriter<Object> writer =
        new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
        originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
        // 写入磁盘缓存
        helper.getDiskCache().put(originalKey, writer); 
        ...
    } finally {
    
    
        loadData.fetcher.cleanup();
    }
    //
    sourceCacheGenerator =
        new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
}

This writes a picture requested by the network to the disk cache

active cache write

So when is the memory cache written?
When we write to the disk, we can get the picture from the disk, so as to execute the onResourceReady method mentioned above

public void onResourceReady(Resource<R> resource, DataSource dataSource) {
    
    
    this.resource = resource;
    this.dataSource = dataSource;
    MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
}

Call back the handleResultOnMainThread() method

private static class MainThreadCallback implements Handler.Callback {
    
    
    @Synthetic
    @SuppressWarnings("WeakerAccess")
    MainThreadCallback() {
    
     }
    @Override
    public boolean handleMessage(Message message) {
    
    
        EngineJob<?> job = (EngineJob<?>) message.obj;
        switch (message.what) {
    
    
            case MSG_COMPLETE:
            job.handleResultOnMainThread();
            ......
        }
        return true;
    }
}

Then we come to the handleResultOnMainThread() method, go in and see what has been done:

  • Step 1: use count +1, prepare for putting into active cache
  • Step 2: callback to EngineJob
  • Step 3: Use count -1, prepare for putting into active cache
void handleResultOnMainThread() {
    
    
    ...
    engineResource = engineResourceFactory.build(resource, isCacheable);
    hasResource = true;
    //第一步:使用计数,为了放入活动缓存中准备
    engineResource.acquire(); 
    //第二步:回调给EngineJob
    listener.onEngineJobComplete(this, key, engineResource); 
    for (int i = 0, size = cbs.size(); i < size; i++) {
    
    
    ResourceCallback cb = cbs.get(i);
    if (!isInIgnoredCallbacks(cb)) {
    
    
        engineResource.acquire();
        cb.onResourceReady(engineResource, dataSource);
    }
}
    //第三步:使用计数,为了放入活动缓存中准备
    engineResource.release(); 

    release(false /*isRemovedFromQueue*/);
}

Let's look at the second step first: what did the callback do to EngineJob?
Obviously, if the resource does not use activeResources.activate() for the air conditioner, this method is to store the resource here in the weak reference cache.

public void onEngineJobComplete(EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
    
    
    Util.assertMainThread();
    if (resource != null) {
    
    
        resource.setResourceListener(key, this);
        if (resource.isCacheable()) {
    
    
            activeResources.activate(key, resource);
        }
    }
    jobs.removeIfCurrent(key, engineJob);
}

void activate(Key key, EngineResource<?> resource) {
    
    
    ResourceWeakReference toPut =
    new ResourceWeakReference(
            key,
    resource,
    getReferenceQueue(),
    isActiveResourceRetentionAllowed);
    ResourceWeakReference removed = activeEngineResources.put(key, toPut); //传入弱引用缓存
    if (removed != null) {
    
    
        removed.reset();
    }
}

So far the weak reference cache is also written.

memcache write

So when is the memory cache written?
In fact, this has already been mentioned before.
The acquire() and release() methods of engineResource:

acquire() 每调用一次引用计数 acquired 加1
release() 方法每调用一次 acquired 减1

The reference count acquired indicates the number of users currently using the resource, greater than 0 indicates that the resource is in use, and a value of 0 indicates that no user is using it and it needs to be written into the memory cache at the moment, calling onResourceReleased() in release() will not The resources used are written into the memory cache and still return to the onResourceReleased() method in Engine:

private final MemoryCache cache;

public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    
    
    Util.assertMainThread();
    activeResources.deactivate(cacheKey); //先从弱引用缓存中移除
    if (resource.isCacheable()) {
    
    
        cache.put(cacheKey, resource); //写入内存缓存
    } else {
    
    
        resourceRecycler.recycle(resource);
    }
}

Since there is no user for this picture, call activeResources.deactivate() to clear it from the weak reference cache first, and then write the data into the memory cache. The cache is of type MemoryCache, and MemoryCache is an interface class whose implementor is LruCache.

It can be seen that the picture in use is saved as a weak reference using activeResources. Glide sets a reference count variable acquired for the picture to count the current number of references to the picture. If acquired is 0, it means that the picture has no users, and the picture is saved Removed from weak reference cache and saved to LruCache. That is, in the memory cache.

Summarize:

We have analyzed the source of reading from the L3 cache, the acquisition of the L3 cache, and the writing of the L3 cache.
In a nutshell:

  • Cache reading order:
    active cache -> LruCache memory cache -> DiskLruCache disk cache
  • Cache writing order:
    DiskLruCache disk cache original image -> active cache -> LruCache memory cache (join when acquired is 0)

The role of active cache (weak reference cache)

Through the following three functions, explain its function
Function 1:
First, through the above knowledge, we know that there are two timings for writing the active cache:

  • After the network request, it is added to the disk cache, and then it is added to the active cache by engineResource.acquire(); after counting +1
  • After the cache is acquired in the memory cache LRU, it is added to the active cache after being used by engineResource.acquire(); counting +1

Only added to the memory cache after engineResource.release(); count -1. Otherwise, loading it into the memory cache every time will overload the memory cache, and the pressure will be great! ! ! !

Function 2:
Use the method of weak reference object reference, which will be recycled immediately when GC recycles, reducing the occurrence of memory leaks.
Function 3:
Prevent LRU from reclaiming objects currently in use. The algorithm follows the principles of recent use frequency and recent use time.
The basic idea is that pages that have not been used for a long time are considered the least recently used and should therefore be eliminated first.
When a large number of images are used frequently, there will be a situation where the images in use are recycled.

Summarize:

  • Reduce memory cache pressure
  • Reduce memory leak problems
  • Prevent LRU from reclaiming resources that are in use

The cache configuration uses

Customize the size of some caches

@GlideModule
public final class CustomGlideModule extends AppGlideModule {
    
    
    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
    
    
        // 设置缓存大小为20mb
        int memoryCacheSizeBytes = 1024 * 1024 * 20; // 20mb
        // 设置内存缓存大小
        builder.setMemoryCache(new LruResourceCache(memoryCacheSizeBytes));
        // 根据SD卡是否可用选择是在内部缓存还是SD卡缓存
        if(SDCardUtils.isSDCardEnable()){
    
    
            builder.setDiskCache(new ExternalPreferredCacheDiskCacheFactory(context, "HYManagerImages", memoryCacheSizeBytes));
        }else {
    
    
            builder.setDiskCache(new InternalCacheDiskCacheFactory(context, "HYManagerImages", memoryCacheSizeBytes));
        }
    }
    // 针对V4用户可以提升速度
    @Override
    public boolean isManifestParsingEnabled() {
    
    
        return false;
    }
}

Glide skips memory cache

GlideApp.with(context)
    .load(url)
    .skipMemoryCache(true)//默认为false
    .dontAnimate()
    .centerCrop()
    .into(imageView);

Glide disk cache policy configuration

GlideApp.with(context)
            .load(url)
            .diskCacheStrategy(DiskCacheStrategy.ALL)
            .dontAnimate()
            .centerCrop()
            .into(imageView);
/*默认的策略是DiskCacheStrategy.AUTOMATIC 
DiskCacheStrategy有五个常量:
DiskCacheStrategy.ALL 使用DATA和RESOURCE缓存远程数据,仅使用RESOURCE来缓存本地数据。
DiskCacheStrategy.NONE 不使用磁盘缓存
DiskCacheStrategy.DATA 在资源解码前就将原始数据写入磁盘缓存
DiskCacheStrategy.RESOURCE 在资源解码后将数据写入磁盘缓存,即经过缩放等转换后的图片资源。
DiskCacheStrategy.AUTOMATIC 根据原始图片数据和资源编码策略来自动选择磁盘缓存策略。*/

cache cleaning

//磁盘缓存清理(子线程)
GlideApp.get(context).clearDiskCache();
//内存缓存清理(主线程)
GlideApp.get(context).clearMemory();

Handwritten LRU Algorithm

Explanation of the LRU algorithm in Android

The LRU algorithm follows the principles of recent use frequency and recent use time. The basic idea is that pages that have not been used for a long time are considered the least recently used and should therefore be eliminated first.
The implementation of the LRU algorithm in Android uses the new LinkedHashMap in Api

private final Map<T, LruCache.Entry<Y>> cache = new LinkedHashMap(100, 0.75F, true);

Explain his three parameters:

  • Parameter one (100): Capacity parameter, indicating the maximum number of elements that the LinkedHashMap can store. When this number is exceeded, LinkedHashMap will automatically eliminate the least recently used elements according to the principle of the LRU algorithm.
  • Parameter 2 (0.75F): loading factor, indicating the percentage of the element filled to capacity. When this percentage is reached, LinkedHashMap will perform expansion operations to reduce the probability of hash collisions.
  • Parameter three (true): the access order parameter. If this parameter is set to true, LinkedHashMap will be sorted according to the access order (the most recent access is placed at the end, and the earliest access is placed at the head); if it is set to False, the LinkedHashMap will be sorted in insertion order.

That is, by adjusting parameters such as capacity, load factor, and access order, you can control the size of the cache and the elimination strategy to optimize the fast access to elements that are frequently used recently.

Implementation of LRU algorithm

Let's write the implementation of the LRU algorithm by hand:
a few points need to be clarified before the implementation:
The LRU cache mechanism can be implemented through a hash table supplemented by a doubly linked list. We use a hash table and a doubly linked list to maintain all the data stored in the cache. key-value pairs.

  • The doubly linked list stores these key-value pairs in the order they are used, the key-value pairs near the head are the most recently used, and the key-value pairs near the tail are the least used.
  • The hash table is an ordinary hash map (HashMap), which maps the key of the cached data to its position in the doubly linked list.

In this way, we first use the hash table to locate, find out the position of the cache item in the doubly linked list, and then move it to the head of the doubly linked list, which can be done in O(1)O(1)O(1) The get or put operation is completed within the specified time. The specific method is as follows:

  • For the get operation, first determine whether the key exists:
    • If key does not exist, return −1-1−1;
    • If the key exists, the node corresponding to the key is the most recently used node. Locate the position of the node in the doubly linked list through the hash table, move it to the head of the doubly linked list, and finally return the value of the node.
  • For the put operation, first determine whether the key exists:
    • If the key does not exist, create a new node with the key and value, add the node at the head of the doubly linked list, and add the key and the node into the hash table. Then judge whether the number of nodes of the doubly linked list exceeds the capacity, if it exceeds the capacity, delete the tail node of the doubly linked list, and delete the corresponding item in the hash table;
    • If the key exists, it is similar to the get operation, first locate through the hash table, then update the value of the corresponding node to value, and move the node to the head of the doubly linked list.

In the above operations, the time complexity of accessing the hash table is O(1)O(1)O(1), and the complexity of adding nodes at the head of the doubly linked list and deleting nodes at the end of the doubly linked list is also O(1)O(1)O(1). (1)O(1)O(1).

Moving a node to the head of the doubly linked list can be divided into two steps: "deleting the node" and "adding a node at the head of the doubly linked list", both of which can be performed in O(1)O(1)O(1) time completed within.

public class LRUCache {
    
    
    class DLinkedNode {
    
    
        int key;
        int value;
        DLinkedNode prev;
        DLinkedNode next;
        public DLinkedNode() {
    
    }
        public DLinkedNode(int _key, int _value) {
    
    key = _key; value = _value;}
    }

    private Map<Integer, DLinkedNode> cache = new HashMap<Integer, DLinkedNode>();
    private int size;
    private int capacity;
    private DLinkedNode head, tail;

    public LRUCache(int capacity) {
    
    
        this.size = 0;
        this.capacity = capacity;
        // 使用伪头部和伪尾部节点
        head = new DLinkedNode();
        tail = new DLinkedNode();
        head.next = tail;
        tail.prev = head;
    }

    public int get(int key) {
    
    
        DLinkedNode node = cache.get(key);
        if (node == null) {
    
    
            return -1;
        }
        // 如果 key 存在,先通过哈希表定位,再移到头部
        moveToHead(node);
        return node.value;
    }

    public void put(int key, int value) {
    
    
        DLinkedNode node = cache.get(key);
        if (node == null) {
    
    
            // 如果 key 不存在,创建一个新的节点
            DLinkedNode newNode = new DLinkedNode(key, value);
            // 添加进哈希表
            cache.put(key, newNode);
            // 添加至双向链表的头部
            addToHead(newNode);
            ++size;
            if (size > capacity) {
    
    
                // 如果超出容量,删除双向链表的尾部节点
                DLinkedNode tail = removeTail();
                // 删除哈希表中对应的项
                cache.remove(tail.key);
                --size;
            }
        }
        else {
    
    
            // 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
            node.value = value;
            moveToHead(node);
        }
    }

    private void addToHead(DLinkedNode node) {
    
    
        node.prev = head;
        node.next = head.next;
        head.next.prev = node;
        head.next = node;
    }

    private void removeNode(DLinkedNode node) {
    
    
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    private void moveToHead(DLinkedNode node) {
    
    
        removeNode(node);
        addToHead(node);
    }

    private DLinkedNode removeTail() {
    
    
        DLinkedNode res = tail.prev;
        removeNode(res);
        return res;
    }
}

Summarize

come on! ! ! !

Guess you like

Origin blog.csdn.net/weixin_45112340/article/details/132257976