一篇文章搞定《图片框架Glide的三级缓存(全)》

前言

Glide的内容太庞大了,弄在一起简直受不了,三级缓存单拿出来说一说吧。
首先三级缓存是Glide中非常重要的缓存机制,也是这种缓存机制才让我们的图片加载的效率,性能如此的高。
下面我们来看看具体的三级缓存内容

三级缓存的读取

首先Glide的三级缓存包含哪些呢?不同于常见的三级缓存(内存,本地,网络)
Glide的三级缓存描述的是以下三级:(当然网络缓存肯定还有,只是Glide所说的三级缓存不包含网络缓存)

获取缓存的源头(前两级,活动缓存和内存缓存)

他发生在Egine.load() 方法中,也就是 Glide 图片加载的核心方法中。(如果不知道这个方法,肯定是没看之前文章的)
Egine.load() 除了创建我们非常重要的EngineJob还有DecodeJob就是我们的缓存策略了。

哦对了,还有一个EngineKey的生成(ps:就是利用多个参数构建的EngineKey的实体,作为缓存中HashMap的Key键)

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(这里只是活动缓存和内存缓存)(磁盘缓存在下面会说)

@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;
            }
        }
    }
}

获取缓存的源头(第三级,磁盘缓存)

上面说到第三级磁盘缓存在DecodeJob中。为什么分开说呢? 因为磁盘缓存的获取藏得比较深了

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);
}

让我们来具体看一下:
上面前面两级缓存都没有获取到memoryResource 。这个时候调用了waitForExistingOrStartNewJob其实大家猜也猜得到,磁盘缓存在这里。这里稍微细说一下,别嫌弃代码多
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);
这没什么说的,添加decodeJob任务并执行。

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

哪执行线程,会执行什么呢? 那肯定是run啊!!!还用说(简化一下只看磁盘缓存相关)

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

runWrapped()
这个方法中,代表着加载的三个阶段(代码不细看了,我直接描述一下)

  • 一阶段:初始化阶段,调用getNextStage()方法获取下一个阶段,调用getNextGenerator()方法获取下一个生成器,再调用runGenerators()方法执行生成器。
  • 二阶段:切换到源服务阶段,调用runGenerators()方法执行生成器。
  • 三阶段:解码数据阶段,调用decodeFromRetrievedData()方法解码获取到的数据。
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);
    }

}

我们获取图片那肯定是一阶段:初始化阶段,所以我们看关注点一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);
    }
}

这里只是创造出,三个不同情景下的请求对象而已。具体使用,在(关注点二 runGenerators()方法中)

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

紧接着执行了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;
}

首先为当前图片创建一个用于标识图片唯一性的 key,接着很明显了,调用helper.getDiskCache().get(currentKey) 就是从我们的磁盘缓存中获取图片资源,由于这里是 ResourceCacheGenerator 的 startNext(),所以获取到的资源是经过压缩或者转换后的图片。

要获取未经过压缩及转换的图片的话如何获取呢?首先需要我们在设置 RequestOptions 的 diskCacheStrategy() 时设置的 DiskCacheStrategy 类型是可以缓存原始图片的(ALL、DATA、AUTOMATIC 都可以缓存原始图片),接着 Glide 内部通过调用 DataCacheGenerator 的 startNext() 方法就能获取到原始的图片。

活动缓存详解

  • 原理:弱引用的HashMap
Map<Key, ActiveResources.ResourceWeakReference> activeEngineResources;
  • 范围:当前Activity的生命周期中
  • 作用:分担内存缓存压力、及时的释放内存。
  • 拿取优先级:优先从活动缓存获取
  • 源码解析证明
    获取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;
    }
}

可以看到就是从弱引用Map中获取。(怎么放的别着急,后面会说。这里只是将活动缓存概念说一下)

内存缓存详解

  • 原理:LruCache是利用LinkedHashMap的LRU进行管理的,并进行包装提供MemoryCache接口类的一些方法,比如put、remove
public class LruCache<T, Y> {
    
    
    private final Map<T, LruCache.Entry<Y>> cache = new LinkedHashMap(100, 0.75F, true);
  • 范围:某个App范围,应用完全退出就不存在
  • 作用:加快数据读取、减少磁盘IO操作和网络请求
  • 拿取优先级:活动缓存没有就在内存缓存中寻找
  • 源码解析证明
    获取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;
}

步骤一:获取资源getEngineResourceFromCache
分为如下两步:

  • 通过Key获取图片资源,并移除(这个是LruCache管理的功劳,后面我们来手写LRU算法!!!)
  • 包装EngineResource并返回
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;
}

步骤二:对图片的使用进行计数
步骤三:把图片缓存通过弱引用包装,放到活动缓存的Map中进行管理

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!内存缓存也说完了,并且还解答了活动缓存时用哪里来了,就是在内存缓存中,被使用了,就会将缓存交给活动缓存区管理(后面会将活动缓存的作用和设计初衷)

磁盘缓存详解

  • 原理:DiskLruCache是利用LinkedHashMap的LRU进行管理的,利用DiskCache接口和DiskLruCacheWrapper包装类进行使用。
public final class DiskLruCache implements Closeable {
    
    
    private final LinkedHashMap<String, Entry> lruEntries =
    new LinkedHashMap<String, Entry>(0, 0.75f, true);

当前管理的DiskLruCache的LRU的LinkedHashMap中的Key和Value分别为:

Key:表示缓存条目的键,即每个缓存条目的唯一标识(根据URL获取的)。
Value:表示缓存条目的具体内容,是一个Entry对象,其中包含了缓存条目的文件路径、缓存大小、缓存的读写锁等信息。

  • 范围:整个系统,只要不删除数据,就一直存在
  • 作用:进行永久性保存
  • 拿取优先级:内存缓存没有,就去磁盘缓存读取
  • 源码证明:已经在上面的获取缓存源头(第三级,磁盘缓存中进行了详细的说明)
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());
  ....
}

当磁盘缓存没有获取到,就需要进行网络加载了。

三级缓存的写入

我们从网络请求一张图片开始吧。

磁盘缓存写入

因为磁盘缓存中没有该图片,从而引发起的网络请求返回了图片:

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()方法
    }
    ...
}

这里通过InputStream流从网络中获取了图片,回调给了onDataReady
onDataReady做了两件事

  • 赋值dataToCache对象图片数据,回调cb.reschedule()触发SourceGenerator 的 startNext() 方法进行缓存原图
  • 直接回调给onDataFetcherReady(不需要缓存的话)
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); 
    }
}

紧接着,在磁盘获取的时候 getNextGenerator() 如果返回的是 SourceGenerator 时,表明需要去请求图片,进入 SourceGenerator 的 startNext() 方法:(这个源码上面在获取缓存源头(第三级中有说到))

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

那么看看cacheData做了什么

  • 编码(压缩或转换)图片encoder
  • 写入磁盘缓存(原图)
  • 创建DataCacheGenerator写入压缩、编码后的图片到磁盘缓存中
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);
}

这样就把一个网络请求的图片写入到了磁盘缓存

活动缓存写入

那么内存缓存是什么时候写入的呢?
当我们写入磁盘后,就可以从磁盘获取到图片,从而执行前面说到的onResourceReady方法

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

回调handleResultOnMainThread()方法

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;
    }
}

那我们来到handleResultOnMainThread() 方法,进去看看都做了什么:

  • 第一步:使用计数+1,为了放入活动缓存中准备
  • 第二步:回调给EngineJob
  • 第三步: 使用计数-1,为了放入活动缓存中准备
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*/);
}

先来看第二步:回调给EngineJob都做了什么?
很明显,如果 resource 不为空调用 activeResources.activate(),这个方法就是将我们这里的 resource 存入了弱引用缓存中。

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();
    }
}

至此弱引用缓存也被写入。

内存缓存写入

那么内存缓存又是什么时候写入的呢?
其实这个在前面已经讲到了。
engineResource 的 acquire() 和 release() 方法:

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

引用计数 acquired 表示当前正在使用资源的使用者数,大于0表示资源正在使用中,值为0表示没有使用者使用此刻就需要将它写入内存缓存中,release() 中调用 onResourceReleased() 将没有使用的资源写入内存缓存,仍然又回到了 Engine 中的 onResourceReleased() 方法:

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);
    }
}

由于这个图片当前已没有使用者,调用 activeResources.deactivate() 先把它从弱引用缓存中清除,然后就是将数据写入内存缓存,cache 是 MemoryCache 类型的,MemoryCache 是一个接口类它的实现者就是 LruCache。

由此可见,正在使用的图片使用 activeResources 以弱引用的方式保存起来,Glide 给图片设置了一个引用计数变量 acquired 用于统计图片当前的引用数,acquired 为0即为图片没有使用者,就将图片从弱引用缓存中移除然后保存到 LruCache 中。也就是内存缓存中。

总结:

我们从三级缓存读取的源头、到三级缓存的获取、到三级缓存的写入都分析了。
简单总结来说:

  • 缓存读取顺序:
    活动缓存 -> LruCache内存缓存 -> DiskLruCache磁盘缓存
  • 缓存写入顺序:
    DiskLruCache 磁盘缓存原图 -> 活动缓存 -> LruCache内存缓存(acquired为0时加入)

活动缓存(弱引用缓存)的作用

通过下面三个功能,解释他的作用
功能一:
首先通过上面的知识,我们知道活动缓存的写入有如下两个时机:

  • 网络请求后、加入磁盘缓存、之后在使用时被engineResource.acquire(); 计数+1后加入到活动缓存中
  • 在内存缓存LRU中获取到缓存后、在使用时被engineResource.acquire(); 计数+1后加入到活动缓存中

只有在engineResource.release(); 计数-1后加入到内存缓存中。不然每次都加载到内存缓存中,会让内存缓存中超负荷,压力很大的呢!!!!

功能二:
采用弱引用对象引用的方式,在GC回收时会立马被回收,减少内存泄漏的情况发生。
功能三:
防止LRU回收当前正在使用的对象,算法遵循近期使用频率和最近使用时间的原则。
基本思想是,较长时间未被使用的页面会被认为是最近最少使用的,因此应该最先被淘汰出去。
在使用频繁大量的图片时会出现回收正在使用的图片的情况。

总结:

  • 减轻内存缓存压力
  • 减少内存泄漏问题
  • 防止LRU回收正在使用的资源

缓存的配置使用

自定义一些缓存的大小

@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跳过内存缓存

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

Glide磁盘缓存策略配置

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 根据原始图片数据和资源编码策略来自动选择磁盘缓存策略。*/

缓存清理

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

手写LRU算法

对Android中LRU算法的解释

LRU算法遵循近期使用频率和最近使用时间的原则。基本思想是,较长时间未被使用的页面会被认为是最近最少使用的,因此应该最先被淘汰出去。
LRU算法在Android中的实现是使用了Api中new LinkedHashMap

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

解释一下他的三个参数:

  • 参数一(100):容量参数,表示该LinkedHashMap可以存储的最大元素数量。当超过这个数量时,LinkedHashMap会自动按照LRU算法原则淘汰最近最少使用的元素。
  • 参数二(0.75F):加载因子,表示元素填充到容量的百分比。当达到这个百分比时,LinkedHashMap将会进行扩容操作,以减少哈希冲突的概率。
  • 参数三(true):访问顺序参数,如果将此参数设置为true,则LinkedHashMap将会按照访问顺序(最近访问的放在末尾,最早访问的放在头部)进行排序;如果将其设置为false,则LinkedHashMap将按照插入顺序进行排序。

也就是通过调整容量、加载因子和访问顺序等参数,可以控制缓存的大小和淘汰策略,用于优化对于最近使用频率高的元素的快速访问。

LRU算法的实现

让我们来手写对LRU算法的实现吧:
实现之前需要明确的几个点:
LRU 缓存机制可以通过哈希表辅以双向链表实现,我们用一个哈希表和一个双向链表维护所有在缓存中的键值对。

  • 双向链表按照被使用的顺序存储了这些键值对,靠近头部的键值对是最近使用的,而靠近尾部的键值对是最久未使用的。
  • 哈希表即为普通的哈希映射(HashMap),通过缓存数据的键映射到其在双向链表中的位置。

这样一来,我们首先使用哈希表进行定位,找出缓存项在双向链表中的位置,随后将其移动到双向链表的头部,即可在 O(1)O(1)O(1) 的时间内完成 get 或者 put 操作。具体的方法如下:

  • 对于 get 操作,首先判断 key 是否存在:
    • 如果 key 不存在,则返回 −1-1−1;
    • 如果 key 存在,则 key 对应的节点是最近被使用的节点。通过哈希表定位到该节点在双向链表中的位置,并将其移动到双向链表的头部,最后返回该节点的值。
  • 对于 put 操作,首先判断 key 是否存在:
    • 如果 key 不存在,使用 key 和 value 创建一个新的节点,在双向链表的头部添加该节点,并将 key 和该节点添加进哈希表中。然后判断双向链表的节点数是否超出容量,如果超出容量,则删除双向链表的尾部节点,并删除哈希表中对应的项;
    • 如果 key 存在,则与 get 操作类似,先通过哈希表定位,再将对应的节点的值更新为 value,并将该节点移到双向链表的头部。

上述各项操作中,访问哈希表的时间复杂度为 O(1)O(1)O(1),在双向链表的头部添加节点、在双向链表的尾部删除节点的复杂度也为 O(1)O(1)O(1)。

而将一个节点移到双向链表的头部,可以分成「删除该节点」和「在双向链表的头部添加节点」两步操作,都可以在 O(1)O(1)O(1) 时间内完成。

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;
    }
}

总结

加油!!!!

猜你喜欢

转载自blog.csdn.net/weixin_45112340/article/details/132257976