Android Glide 缓存模块源码深度解析
一、引言
在 Android 开发领域,图片加载是一个极为常见且关键的功能。Glide 作为一款被广泛使用的图片加载库,其缓存模块是提升图片加载效率和性能的核心组件。合理的缓存机制能够显著减少网络请求,降低流量消耗,同时加快图片显示速度,为用户带来流畅的使用体验。本文将深入 Glide 缓存模块的源码,从整体架构到具体实现细节,全方位剖析其工作原理。
二、Glide 缓存模块概述
2.1 缓存的重要性
在移动应用中,图片资源通常占据较大的存储空间和网络带宽。频繁的网络请求不仅会增加用户的流量成本,还会导致图片加载缓慢,影响用户体验。通过缓存机制,Glide 可以将已经加载过的图片存储在本地,下次需要加载相同图片时,直接从缓存中获取,从而避免重复的网络请求,提高加载速度。
2.2 缓存的类型
Glide 提供了两种主要的缓存类型:内存缓存和磁盘缓存。
- 内存缓存:将图片存储在内存中,读取速度极快,能够实现图片的快速显示。Glide 默认使用 LRU(Least Recently Used,最近最少使用)算法来管理内存缓存,确保在内存有限的情况下,优先保留最近使用的图片。
- 磁盘缓存:将图片存储在设备的磁盘上,适用于长期保存图片。Glide 使用 DiskLruCache 来实现磁盘缓存,同样采用 LRU 算法进行管理,保证磁盘空间的合理利用。
2.3 缓存的级别
Glide 的缓存分为多个级别,按照查找顺序依次为:
- 活动资源缓存(Active Resources) :存储正在被使用的图片资源,避免重复加载。
- 内存缓存(Memory Cache) :存储最近使用过的图片资源,读取速度较快。
- 磁盘缓存(Disk Cache) :存储已经下载过的图片资源,用于长期保存。
- 网络请求(Network Request) :当缓存中没有所需的图片时,才会发起网络请求。
三、内存缓存源码分析
3.1 核心类概述
Glide 的内存缓存主要由以下几个核心类实现:
LruResourceCache
:继承自 Android 系统的LruCache
,是 Glide 默认的内存缓存实现,使用 LRU 算法管理缓存。MemoryCache
:是一个接口,定义了内存缓存的基本操作,如put
、get
、remove
等。ActiveResources
:用于管理活动资源缓存,存储正在使用的图片资源。
3.2 LruResourceCache 类分析
3.2.1 类定义及属性
java
import android.util.LruCache;
import com.bumptech.glide.load.Key;
import com.bumptech.glide.load.engine.Resource;// LruResourceCache 类继承自 LruCache,用于实现 Glide 的内存缓存
public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache {// 资源移除监听器,当缓存项被移除时会触发该监听器private ResourceRemovedListener listener;/*** 构造函数,初始化 LruResourceCache 实例* @param size 缓存的最大大小,单位为字节*/public LruResourceCache(int size) {super(size);}@Overridepublic void setResourceRemovedListener(ResourceRemovedListener listener) {// 设置资源移除监听器this.listener = listener;}@Overrideprotected int sizeOf(Key key, Resource<?> resource) {// 计算资源的大小,用于 LRU 算法的缓存管理return resource.getSize();}@Overrideprotected void entryRemoved(boolean evicted, Key key, Resource<?> oldValue, Resource<?> newValue) {// 当缓存项被移除时,调用资源移除监听器的回调方法if (listener != null && oldValue != null) {listener.onResourceRemoved(oldValue);}}@Overridepublic Resource<?> put(Key key, Resource<?> resource) {// 向缓存中添加资源if (key == null || resource == null) {return null;}return super.put(key, resource);}@Overridepublic Resource<?> remove(Key key) {// 从缓存中移除资源if (key == null) {return null;}return super.remove(key);}@Overridepublic void clearMemory() {// 清空缓存evictAll();}@Overridepublic void trimMemory(int level) {// 根据系统内存状态进行缓存清理if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {clearMemory();} else if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN|| level == android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {trimToSize(getMaxSize() / 2);}}
}
3.2.2 详细分析
- 构造函数:接收一个
size
参数,用于指定缓存的最大大小。在初始化时,调用父类LruCache
的构造函数进行初始化。 sizeOf
方法:该方法用于计算每个缓存项的大小,LruCache
会根据这个大小来判断缓存是否已满。resource.getSize()
方法返回资源的实际大小。entryRemoved
方法:当缓存项被移除时,会调用该方法。如果设置了ResourceRemovedListener
,则会触发监听器的onResourceRemoved
方法,通知外部资源已被移除。put
方法:向缓存中添加资源,首先检查key
和resource
是否为null
,如果不为null
,则调用父类的put
方法将资源添加到缓存中。remove
方法:从缓存中移除指定key
的资源,同样会先检查key
是否为null
。clearMemory
方法:调用evictAll
方法清空缓存。trimMemory
方法:根据系统的内存状态进行缓存清理。当系统内存不足时,会根据不同的内存级别进行相应的清理操作。
3.3 ActiveResources 类分析
3.3.1 类定义及属性
java
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bumptech.glide.load.Key;
import com.bumptech.glide.load.engine.Resource;
import com.bumptech.glide.util.Synthetic;import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;// ActiveResources 类用于管理活动资源缓存,存储正在使用的图片资源
public class ActiveResources {// 存储活动资源的弱引用,使用 Key 作为键private final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();// 资源释放监听器,当活动资源被释放时会触发该监听器private final ResourceListener listener;// 是否允许保留活动资源private final boolean isActiveResourceRetentionAllowed;// 日志标签private static final String TAG = "ActiveResources";// 引用队列,用于处理被垃圾回收的弱引用private final ReferenceQueue<Resource<?>> resourceReferenceQueue = new ReferenceQueue<>();/*** 构造函数,初始化 ActiveResources 实例* @param listener 资源释放监听器* @param isActiveResourceRetentionAllowed 是否允许保留活动资源*/public ActiveResources(ResourceListener listener, boolean isActiveResourceRetentionAllowed) {this.listener = listener;this.isActiveResourceRetentionAllowed = isActiveResourceRetentionAllowed;}// 内部类,用于存储资源的弱引用@Syntheticstatic final class ResourceWeakReference extends WeakReference<Resource<?>> {// 资源的 Key@NonNullfinal Key key;// 资源是否可缓存final boolean isCacheable;ResourceWeakReference(@NonNull Key key,@NonNull Resource<?> referent,@NonNull ReferenceQueue<? super Resource<?>> queue,boolean isCacheable) {super(referent, queue);this.key = key;this.isCacheable = isCacheable;}}/*** 获取活动资源* @param key 资源的 Key* @return 活动资源,如果不存在则返回 null*/@Nullablepublic Resource<?> get(Key key) {// 从活动资源映射中获取资源的弱引用ResourceWeakReference activeRef = activeEngineResources.get(key);if (activeRef == null) {return null;}// 获取弱引用指向的资源Resource<?> active = activeRef.get();if (active == null) {// 如果资源已经被垃圾回收,清理该弱引用cleanupActiveReference(activeRef);}return active;}/*** 激活资源,将资源添加到活动资源缓存中* @param key 资源的 Key* @param resource 资源对象*/public void activate(Key key, Resource<?> resource) {// 创建资源的弱引用ResourceWeakReference toPut =new ResourceWeakReference(key, resource, resourceReferenceQueue, resource.isCacheable());// 将弱引用添加到活动资源映射中ResourceWeakReference removed = activeEngineResources.put(key, toPut);if (removed != null) {// 如果已经存在相同 Key 的弱引用,清除旧的引用removed.clear();cleanupActiveReference(removed);}}/*** 释放资源,将资源从活动资源缓存中移除* @param key 资源的 Key*/public void deactivate(Key key) {// 从活动资源映射中移除指定 Key 的弱引用ResourceWeakReference removed = activeEngineResources.remove(key);if (removed != null) {// 如果移除成功,通知资源释放监听器listener.onResourceReleased(key, removed.get());}}private void cleanupActiveReference(@NonNull ResourceWeakReference ref) {synchronized (this) {// 从活动资源映射中移除指定的弱引用activeEngineResources.remove(ref.key);}if (ref.isCacheable) {// 如果资源可缓存,通知资源释放监听器listener.onResourceReleased(ref.key, ref.get());}}
}
3.3.2 详细分析
- 构造函数:接收一个
ResourceListener
和一个布尔值isActiveResourceRetentionAllowed
作为参数。ResourceListener
用于监听资源的释放事件,isActiveResourceRetentionAllowed
表示是否允许保留活动资源。 ResourceWeakReference
内部类:继承自WeakReference
,用于存储资源的弱引用。key
用于唯一标识资源,isCacheable
表示资源是否可缓存。get
方法:根据key
从activeEngineResources
中获取资源的弱引用。如果弱引用不为null
,则获取其指向的资源。如果资源已经被垃圾回收,则调用cleanupActiveReference
方法清理该弱引用。activate
方法:将资源添加到活动资源缓存中。首先创建资源的弱引用,然后将其添加到activeEngineResources
中。如果已经存在相同key
的弱引用,则清除旧的引用。deactivate
方法:将资源从活动资源缓存中移除。从activeEngineResources
中移除指定key
的弱引用,并通知ResourceListener
资源已被释放。cleanupActiveReference
方法:从activeEngineResources
中移除指定的弱引用。如果资源可缓存,则通知ResourceListener
资源已被释放。
3.4 内存缓存的使用流程
在 Glide 的 Engine
类中,会优先从活动资源缓存和内存缓存中查找所需的图片资源。以下是 Engine
类中获取资源的部分关键代码:
java
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bumptech.glide.load.Key;
import com.bumptech.glide.load.engine.EngineJob;
import com.bumptech.glide.load.engine.EngineResource;
import com.bumptech.glide.load.engine.Resource;
import com.bumptech.glide.load.engine.cache.MemoryCache;
import com.bumptech.glide.load.engine.executor.GlideExecutor;
import com.bumptech.glide.util.Preconditions;import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;// Engine 类是 Glide 的核心引擎,负责协调资源的加载和缓存
public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener {// 活动资源管理器,用于管理活动资源缓存private final ActiveResources activeResources;// 内存缓存,用于存储最近使用过的资源private final MemoryCache memoryCache;// 存储正在进行的引擎作业,使用 Key 作为键private final Map<Key, EngineJob<?>> jobs = new HashMap<>();// 用于执行磁盘缓存任务的线程池private final ExecutorService diskCacheService;// 用于执行源数据加载任务的线程池private final ExecutorService sourceService;/*** 构造函数,初始化 Engine 实例* @param memoryCache 内存缓存实例* @param diskCacheService 磁盘缓存线程池* @param sourceService 源数据加载线程池*/public Engine(MemoryCache memoryCache,ExecutorService diskCacheService,ExecutorService sourceService) {this.memoryCache = memoryCache;this.diskCacheService = diskCacheService;this.sourceService = sourceService;this.activeResources = new ActiveResources(true);// 设置内存缓存的资源移除监听器为当前 Engine 实例memoryCache.setResourceRemovedListener(this);}/*** 加载资源的核心方法* @param context 上下文对象* @param model 图片来源的模型对象* @param key 资源的 Key* @param signature 资源的签名* @param width 图片的宽度* @param height 图片的高度* @param transformations 图片的转换操作* @param resourceClass 资源的类类型* @param priority 加载的优先级* @param diskCacheStrategy 磁盘缓存策略* @param isSkipMemoryCache 是否跳过内存缓存* @param onlyRetrieveFromCache 是否仅从缓存中获取资源* @param options 加载选项* @param listener 引擎作业监听器* @return 引擎作业实例*/@NonNullpublic <R> EngineJob<R> load(@NonNull Context context,@NonNull Object model,@NonNull Key key,@NonNull Key signature,int width,int height,@NonNull Map<Class<?>, Transformation<?>> transformations,@NonNull Class<R> resourceClass,@NonNull Priority priority,@NonNull DiskCacheStrategy diskCacheStrategy,boolean isSkipMemoryCache,boolean onlyRetrieveFromCache,@NonNull Options options,@NonNull EngineJobListener listener) {// 首先从活动资源缓存中查找资源EngineResource<?> active = loadFromActiveResources(key, isSkipMemoryCache);if (active != null) {// 如果找到资源,通知监听器作业完成listener.onEngineJobComplete(null, active);return null;}// 然后从内存缓存中查找资源EngineResource<?> cached = loadFromCache(key, isSkipMemoryCache);if (cached != null) {// 如果找到资源,通知监听器作业完成listener.onEngineJobComplete(null, cached);return null;}// 如果缓存中没有找到资源,创建引擎作业并开始加载EngineJob<R> engineJob =EngineJobFactory.build(key,isSkipMemoryCache,false, // useUnlimitedSourceExecutorPoolfalse, // useAnimationPoolonlyRetrieveFromCache,diskCacheService,sourceService,listener);jobs.put(key, engineJob);engineJob.start(new LoadPathCacheStrategyWrapper<>(model, signature, width, height, transformations, resourceClass, priority, diskCacheStrategy, isSkipMemoryCache, onlyRetrieveFromCache, options, engineJob));return engineJob;}private EngineResource<?> loadFromActiveResources(Key key, boolean isSkipMemoryCache) {if (isSkipMemoryCache) {return null;}// 从活动资源缓存中获取资源Resource<?> active = activeResources.get(key);if (active != null) {// 如果找到资源,增加资源的引用计数active.acquire();return new EngineResource<>(active, true, true);}return null;}private EngineResource<?> loadFromCache(Key key, boolean isSkipMemoryCache) {if (isSkipMemoryCache) {return null;}// 从内存缓存中获取资源Resource<?> cached = memoryCache.remove(key);if (cached != null) {// 如果找到资源,将其包装成 EngineResourceif (cached instanceof EngineResource) {return (EngineResource<?>) cached;} else {return new EngineResource<>(cached, true, false);}}return null;}@Overridepublic void onResourceRemoved(@NonNull Resource<?> removed) {// 当资源从内存缓存中移除时,将其从活动资源缓存中释放activeResources.deactivate(removed.getKey());}
}
3.4.1 详细分析
- 构造函数:初始化
activeResources
、memoryCache
、diskCacheService
和sourceService
等属性,并将当前Engine
实例设置为memoryCache
的资源移除监听器。 load
方法:是资源加载的核心方法。首先调用loadFromActiveResources
方法从活动资源缓存中查找资源,如果找到则通知监听器作业完成并返回null
。如果活动资源缓存中没有找到,则调用loadFromCache
方法从内存缓存中查找资源。如果内存缓存中也没有找到,则创建EngineJob
并开始加载资源。loadFromActiveResources
方法:根据key
从activeResources
中获取资源。如果找到资源,则增加资源的引用计数,并将其包装成EngineResource
返回。loadFromCache
方法:根据key
从memoryCache
中移除资源。如果找到资源,则将其包装成EngineResource
返回。onResourceRemoved
方法:当资源从内存缓存中移除时,调用activeResources
的deactivate
方法将其从活动资源缓存中释放。
四、磁盘缓存源码分析
4.1 核心类概述
Glide 的磁盘缓存主要由以下几个核心类实现:
DiskLruCacheWrapper
:是 Glide 默认的磁盘缓存实现,基于DiskLruCache
实现。DiskCache
:是一个接口,定义了磁盘缓存的基本操作,如put
、get
、delete
等。DiskCacheStrategy
:是一个枚举类,定义了磁盘缓存的策略,如ALL
、NONE
、DATA
、RESOURCE
等。
4.2 DiskLruCacheWrapper 类分析
4.2.1 类定义及属性
java
import android.content.Context;
import android.os.Environment;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bumptech.glide.load.Key;
import com.bumptech.glide.load.engine.cache.DiskCache;
import com.bumptech.glide.load.engine.cache.DiskLruCacheFactory;
import com.bumptech.glide.load.engine.cache.DiskLruCacheWrapper;
import com.bumptech.glide.load.engine.cache.LruDiskCacheFactory;
import com.bumptech.glide.util.Preconditions;import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;// DiskLruCacheWrapper 类实现了 DiskCache 接口,用于实现 Glide 的磁盘缓存
public class DiskLruCacheWrapper implements DiskCache {// 磁盘缓存的目录private final File directory;// 磁盘缓存的最大大小private final long maxSize;// DiskLruCache 实例,用于实际的磁盘缓存操作private DiskLruCache diskLruCache;// 用于同步访问 DiskLruCache 的锁对象private final Object diskLruCacheLock = new Object();// 磁盘 LRU 缓存是否已经初始化的标志private boolean isDiskLruCacheInitialized;// 日志标签private static final String TAG = "DiskLruCacheWrapper";/*** 构造函数,初始化 DiskLruCacheWrapper 实例* @param directory 磁盘缓存的目录* @param maxSize 磁盘缓存的最大大小*/private DiskLruCacheWrapper(File directory, long maxSize) {this.directory = directory;this.maxSize = maxSize;}/*** 获取 DiskLruCacheWrapper 实例* @param directory 磁盘缓存的目录* @param maxSize 磁盘缓存的最大大小* @return DiskLruCacheWrapper 实例*/@NonNullpublic static DiskCache get(@NonNull File directory, long maxSize) {return new DiskLruCacheWrapper(directory, maxSize);}@Nullable@Overridepublic InputStream get(@NonNull Key key) {synchronized (diskLruCacheLock) {// 确保 DiskLruCache 已经初始化initializeIfNeeded();if (diskLruCache == null) {return null;}try {// 从 DiskLruCache 中获取指定 Key 的快照DiskLruCache.Snapshot snapshot = diskLruCache.get(key.toString());if (snapshot != null) {// 如果快照存在,返回其输入流return snapshot.getInputStream(0);}} catch (IOException e) {if (android.util.Log.isLoggable(TAG, android.util.Log.WARN)) {android.util.Log.w(TAG, "Unable to get from disk cache", e);}}return null;}}@Overridepublic void put(@NonNull Key key, @NonNull Writer writer) {synchronized (diskLruCacheLock) {// 确保 DiskLruCache 已经初始化initializeIfNeeded();if (diskLruCache == null) {return;}DiskLruCache.Editor editor = null;try {// 获取 DiskLruCache 中指定 Key 的编辑器editor = diskLruCache.edit(key.toString());if (editor == null) {throw new IllegalStateException("Had two simultaneous edits to key: " + key);}// 调用 Writer 的 write 方法将数据写入编辑器的输出流if (writer.write(editor.newOutputStream(0))) {// 如果写入成功,提交更改editor.commit();} else {// 如果写入失败,放弃更改editor.abort();}} catch (IOException e) {if (android.util.Log.isLoggable(TAG, android.util.Log.WARN)) {android.util.Log.w(TAG, "Unable to put to disk cache", e);}try {if (editor != null) {// 如果出现异常,放弃更改editor.abort();}} catch (IOException ignored) {// 忽略异常}}}}@Overridepublic void delete(@NonNull Key key) {synchronized (diskLruCacheLock) {// 确保 DiskLruCache 已经初始化initializeIfNeeded();if (diskLruCache != null) {try {// 从 DiskLruCache 中删除指定 Key 的缓存项diskLruCache.remove(key.toString());} catch (IOException e) {if (android.util.Log.isLoggable(TAG, android.util.Log.WARN)) {android.util.Log.w(TAG, "Unable to delete from disk cache", e);}}}}}@Overridepublic void clear() {synchronized (diskLruCacheLock) {// 确保 DiskLruCache 已经初始化initializeIfNeeded();if (diskLruCache != null) {try {// 删除 DiskLruCache 中的所有缓存项diskLruCache.delete();// 重新打开 DiskLruCachediskLruCache = DiskLruCache.open(directory, 1, 1, maxSize);} catch (IOException e) {if (android.util.Log.isLoggable(TAG, android.util.Log.WARN)) {android.util.Log.w(TAG, "Unable to clear disk cache", e);}}}}}private void initializeIfNeeded() {if (!isDiskLruCacheInitialized) {try {// 打开 DiskLruCachediskLruCache = DiskLruCache.open(directory, 1, 1, maxSize);} catch (IOException e) {if (android.util.Log.isLoggable(TAG, android.util.Log.WARN)) {android.util.Log.w(TAG, "Unable to open disk cache", e);}}isDisk
4.2 DiskLruCacheWrapper 类分析
4.2.2 关键方法详细分析
get
方法
java
@Nullable
@Override
public InputStream get(@NonNull Key key) {synchronized (diskLruCacheLock) {// 确保 DiskLruCache 已经初始化initializeIfNeeded();if (diskLruCache == null) {return null;}try {// 从 DiskLruCache 中获取指定 Key 的快照DiskLruCache.Snapshot snapshot = diskLruCache.get(key.toString());if (snapshot != null) {// 如果快照存在,返回其输入流return snapshot.getInputStream(0);}} catch (IOException e) {if (android.util.Log.isLoggable(TAG, android.util.Log.WARN)) {android.util.Log.w(TAG, "Unable to get from disk cache", e);}}return null;}
}
- 线程安全:使用
synchronized
块包裹代码,确保在多线程环境下对DiskLruCache
的访问是线程安全的。因为DiskLruCache
的操作不是线程安全的,所以需要进行同步处理。 - 初始化检查:调用
initializeIfNeeded
方法确保DiskLruCache
已经初始化。如果未初始化,会尝试打开磁盘缓存。 - 获取快照:通过
diskLruCache.get(key.toString())
方法尝试获取指定key
的快照。Snapshot
是DiskLruCache
中的一个对象,代表缓存文件的一个快照,包含了文件的元数据和内容。 - 返回输入流:如果快照存在,通过
snapshot.getInputStream(0)
方法获取其输入流,这里的0
表示获取第一个文件的输入流(在 Glide 的磁盘缓存中,通常一个缓存项只有一个文件)。 - 异常处理:如果在获取快照或输入流的过程中出现
IOException
,会记录警告日志并返回null
。
put
方法
java
@Override
public void put(@NonNull Key key, @NonNull Writer writer) {synchronized (diskLruCacheLock) {// 确保 DiskLruCache 已经初始化initializeIfNeeded();if (diskLruCache == null) {return;}DiskLruCache.Editor editor = null;try {// 获取 DiskLruCache 中指定 Key 的编辑器editor = diskLruCache.edit(key.toString());if (editor == null) {throw new IllegalStateException("Had two simultaneous edits to key: " + key);}// 调用 Writer 的 write 方法将数据写入编辑器的输出流if (writer.write(editor.newOutputStream(0))) {// 如果写入成功,提交更改editor.commit();} else {// 如果写入失败,放弃更改editor.abort();}} catch (IOException e) {if (android.util.Log.isLoggable(TAG, android.util.Log.WARN)) {android.util.Log.w(TAG, "Unable to put to disk cache", e);}try {if (editor != null) {// 如果出现异常,放弃更改editor.abort();}} catch (IOException ignored) {// 忽略异常}}}
}
- 线程安全:同样使用
synchronized
块确保线程安全。 - 初始化检查:调用
initializeIfNeeded
方法确保DiskLruCache
已经初始化。 - 获取编辑器:通过
diskLruCache.edit(key.toString())
方法获取指定key
的编辑器。Editor
用于对磁盘缓存文件进行写入操作。如果返回null
,说明有其他线程正在对相同的key
进行写入操作,抛出异常。 - 写入数据:调用
writer.write(editor.newOutputStream(0))
方法将数据写入编辑器的输出流。Writer
是一个接口,具体的实现类负责将数据写入输出流。 - 提交或放弃更改:如果写入成功,调用
editor.commit()
方法提交更改;如果写入失败,调用editor.abort()
方法放弃更改。 - 异常处理:如果在写入过程中出现
IOException
,记录警告日志并尝试放弃更改。
delete
方法
java
@Override
public void delete(@NonNull Key key) {synchronized (diskLruCacheLock) {// 确保 DiskLruCache 已经初始化initializeIfNeeded();if (diskLruCache != null) {try {// 从 DiskLruCache 中删除指定 Key 的缓存项diskLruCache.remove(key.toString());} catch (IOException e) {if (android.util.Log.isLoggable(TAG, android.util.Log.WARN)) {android.util.Log.w(TAG, "Unable to delete from disk cache", e);}}}}
}
- 线程安全:使用
synchronized
块确保线程安全。 - 初始化检查:调用
initializeIfNeeded
方法确保DiskLruCache
已经初始化。 - 删除缓存项:如果
DiskLruCache
已经初始化,通过diskLruCache.remove(key.toString())
方法删除指定key
的缓存项。 - 异常处理:如果在删除过程中出现
IOException
,记录警告日志。
clear
方法
java
@Override
public void clear() {synchronized (diskLruCacheLock) {// 确保 DiskLruCache 已经初始化initializeIfNeeded();if (diskLruCache != null) {try {// 删除 DiskLruCache 中的所有缓存项diskLruCache.delete();// 重新打开 DiskLruCachediskLruCache = DiskLruCache.open(directory, 1, 1, maxSize);} catch (IOException e) {if (android.util.Log.isLoggable(TAG, android.util.Log.WARN)) {android.util.Log.w(TAG, "Unable to clear disk cache", e);}}}}
}
- 线程安全:使用
synchronized
块确保线程安全。 - 初始化检查:调用
initializeIfNeeded
方法确保DiskLruCache
已经初始化。 - 清空缓存:如果
DiskLruCache
已经初始化,通过diskLruCache.delete()
方法删除所有缓存项。 - 重新初始化:删除所有缓存项后,重新调用
DiskLruCache.open
方法打开磁盘缓存,以便后续继续使用。 - 异常处理:如果在清空或重新打开过程中出现
IOException
,记录警告日志。
4.3 DiskCacheStrategy 枚举类分析
java
import androidx.annotation.NonNull;// DiskCacheStrategy 枚举类定义了不同的磁盘缓存策略
public enum DiskCacheStrategy {/*** 不进行磁盘缓存*/NONE(false, false),/*** 只缓存原始数据*/DATA(true, false),/*** 只缓存转换后的数据*/RESOURCE(false, true),/*** 缓存原始数据和转换后的数据*/ALL(true, true),/*** 自动选择缓存策略*/AUTOMATIC(true, true);// 是否缓存原始数据的标志private final boolean cacheSource;// 是否缓存转换后的数据的标志private final boolean cacheResult;DiskCacheStrategy(boolean cacheSource, boolean cacheResult) {this.cacheSource = cacheSource;this.cacheResult = cacheResult;}/*** 判断是否应该缓存原始数据* @return 如果应该缓存原始数据,返回 true;否则返回 false*/public boolean isCacheSource() {return cacheSource;}/*** 判断是否应该缓存转换后的数据* @return 如果应该缓存转换后的数据,返回 true;否则返回 false*/public boolean isCacheResult() {return cacheResult;}/*** 根据数据源和是否是首次加载判断是否应该进行缓存* @param dataSource 数据源* @param isFirstResource 是否是首次加载的资源* @return 如果应该进行缓存,返回 true;否则返回 false*/public boolean shouldCache(@NonNull DataSource dataSource, boolean isFirstResource) {switch (this) {case NONE:return false;case DATA:return dataSource == DataSource.REMOTE;case RESOURCE:return dataSource != DataSource.MEMORY_CACHE && isFirstResource;case ALL:return dataSource != DataSource.MEMORY_CACHE;case AUTOMATIC:switch (dataSource) {case REMOTE:return true;case DATA_DISK_CACHE:case RESOURCE_DISK_CACHE:return false;case MEMORY_CACHE:return false;default:throw new IllegalArgumentException("Unrecognized data source: " + dataSource);}default:throw new IllegalArgumentException("Unrecognized disk cache strategy: " + this);}}
}
-
枚举值含义:
NONE
:不进行磁盘缓存,即cacheSource
和cacheResult
都为false
。DATA
:只缓存原始数据,cacheSource
为true
,cacheResult
为false
。RESOURCE
:只缓存转换后的数据,cacheSource
为false
,cacheResult
为true
。ALL
:缓存原始数据和转换后的数据,cacheSource
和cacheResult
都为true
。AUTOMATIC
:自动选择缓存策略,默认情况下会缓存原始数据和转换后的数据。
-
shouldCache
方法:根据数据源和是否是首次加载的资源判断是否应该进行缓存。不同的缓存策略有不同的判断逻辑:NONE
:始终返回false
,不进行缓存。DATA
:只有当数据源为REMOTE
(远程网络)时才进行缓存。RESOURCE
:当数据源不是MEMORY_CACHE
且是首次加载的资源时进行缓存。ALL
:当数据源不是MEMORY_CACHE
时进行缓存。AUTOMATIC
:根据不同的数据源进行判断,远程数据源进行缓存,磁盘缓存和内存缓存不进行缓存。
4.4 磁盘缓存的使用流程
在 Glide 的 Engine
类中,当内存缓存中没有找到所需的图片资源时,会尝试从磁盘缓存中查找。以下是 Engine
类中与磁盘缓存相关的部分代码:
java
@NonNull
public <R> EngineJob<R> load(@NonNull Context context,@NonNull Object model,@NonNull Key key,@NonNull Key signature,int width,int height,@NonNull Map<Class<?>, Transformation<?>> transformations,@NonNull Class<R> resourceClass,@NonNull Priority priority,@NonNull DiskCacheStrategy diskCacheStrategy,boolean isSkipMemoryCache,boolean onlyRetrieveFromCache,@NonNull Options options,@NonNull EngineJobListener listener) {// 先从活动资源缓存和内存缓存中查找资源EngineResource<?> active = loadFromActiveResources(key, isSkipMemoryCache);if (active != null) {listener.onEngineJobComplete(null, active);return null;}EngineResource<?> cached = loadFromCache(key, isSkipMemoryCache);if (cached != null) {listener.onEngineJobComplete(null, cached);return null;}// 如果内存缓存中没有找到,根据磁盘缓存策略决定是否从磁盘缓存加载if (diskCacheStrategy.isCacheSource()) {// 创建磁盘缓存加载任务EngineJob<R> diskCacheJob =EngineJobFactory.build(key,isSkipMemoryCache,false, // useUnlimitedSourceExecutorPoolfalse, // useAnimationPoolonlyRetrieveFromCache,diskCacheService,sourceService,listener);jobs.put(key, diskCacheJob);diskCacheJob.start(new DiskCacheGenerator(model, signature, width, height, transformations, resourceClass, priority, diskCacheStrategy, isSkipMemoryCache, onlyRetrieveFromCache, options, diskCacheJob));return diskCacheJob;}// 如果不缓存原始数据,直接从源数据加载EngineJob<R> sourceJob =EngineJobFactory.build(key,isSkipMemoryCache,false, // useUnlimitedSourceExecutorPoolfalse, // useAnimationPoolonlyRetrieveFromCache,diskCacheService,sourceService,listener);jobs.put(key, sourceJob);sourceJob.start(new SourceGenerator(model, signature, width, height, transformations, resourceClass, priority, diskCacheStrategy, isSkipMemoryCache, onlyRetrieveFromCache, options, sourceJob));return sourceJob;
}
- 缓存查找:首先调用
loadFromActiveResources
和loadFromCache
方法从活动资源缓存和内存缓存中查找资源。如果找到资源,通知监听器作业完成并返回null
。 - 磁盘缓存判断:如果内存缓存中没有找到资源,检查
DiskCacheStrategy
的isCacheSource
方法,判断是否应该从磁盘缓存加载资源。 - 磁盘缓存加载:如果
isCacheSource
为true
,创建EngineJob
并使用DiskCacheGenerator
从磁盘缓存中加载资源。 - 源数据加载:如果
isCacheSource
为false
,创建EngineJob
并使用SourceGenerator
直接从源数据(如网络)加载资源。
五、缓存键的生成与管理
5.1 缓存键的作用
缓存键是用于唯一标识一个缓存项的字符串。在 Glide 中,缓存键的生成非常重要,它直接影响到缓存的命中率和数据的一致性。通过合理的缓存键生成策略,可以确保相同的图片资源使用相同的缓存键,从而提高缓存的利用率。
5.2 缓存键的生成类
Glide 中缓存键的生成主要由 Key
接口及其实现类完成。常见的实现类有 ObjectKey
、DataCacheKey
等。
5.2.1 ObjectKey 类分析
java
import androidx.annotation.NonNull;
import com.bumptech.glide.load.Key;import java.security.MessageDigest;// ObjectKey 类用于将任意对象转换为缓存键
public class ObjectKey implements Key {// 要转换为缓存键的原始对象private final Object object;/*** 构造函数,初始化 ObjectKey 实例* @param object 要转换为缓存键的原始对象*/public ObjectKey(@NonNull Object object) {this.object = object;}@Overridepublic boolean equals(Object o) {if (o instanceof ObjectKey) {ObjectKey other = (ObjectKey) o;// 判断两个 ObjectKey 是否相等,通过比较原始对象是否相等return object.equals(other.object);}return false;}@Overridepublic int hashCode() {// 返回原始对象的哈希码return object.hashCode();}@Overridepublic void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {// 将原始对象的字符串表示转换为字节数组,并更新 MessageDigest 对象messageDigest.update(object.toString().getBytes(CHARSET));}@NonNull@Overridepublic String toString() {return "ObjectKey{" + "object=" + object + '}';}
}
- 构造函数:接收一个
object
参数,将其存储在object
属性中。 equals
方法:判断两个ObjectKey
是否相等,通过比较原始对象是否相等来实现。hashCode
方法:返回原始对象的哈希码,用于在哈希表中查找。updateDiskCacheKey
方法:将原始对象的字符串表示转换为字节数组,并更新MessageDigest
对象,用于生成磁盘缓存键。toString
方法:返回ObjectKey
的字符串表示,方便调试。
5.2.2 DataCacheKey 类分析
java
import androidx.annotation.NonNull;
import com.bumptech.glide.load.Key;import java.security.MessageDigest;// DataCacheKey 类用于生成数据缓存键,结合了原始键和签名键
public class DataCacheKey implements Key {// 原始键private final Key originalKey;// 签名键private final Key signature;/*** 构造函数,初始化 DataCacheKey 实例* @param originalKey 原始键* @param signature 签名键*/public DataCacheKey(@NonNull Key originalKey, @NonNull Key signature) {this.originalKey = originalKey;this.signature = signature;}@Overridepublic boolean equals(Object o) {if (o instanceof DataCacheKey) {DataCacheKey other = (DataCacheKey) o;// 判断两个 DataCacheKey 是否相等,需要同时比较原始键和签名键是否相等return originalKey.equals(other.originalKey) && signature.equals(other.signature);}return false;}@Overridepublic int hashCode() {// 根据原始键和签名键的哈希码计算 DataCacheKey 的哈希码return originalKey.hashCode() * 31 + signature.hashCode();}@Overridepublic void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {// 分别调用原始键和签名键的 updateDiskCacheKey 方法更新 MessageDigest 对象originalKey.updateDiskCacheKey(messageDigest);signature.updateDiskCacheKey(messageDigest);}@NonNull@Overridepublic String toString() {return "DataCacheKey{" +"originalKey=" + originalKey +", signature=" + signature +'}';}
}
- 构造函数:接收
originalKey
和signature
两个参数,分别存储原始键和签名键。 equals
方法:判断两个DataCacheKey
是否相等,需要同时比较原始键和签名键是否相等。hashCode
方法:根据原始键和签名键的哈希码计算DataCacheKey
的哈希码,使用originalKey.hashCode() * 31 + signature.hashCode()
的方式确保哈希码的唯一性。updateDiskCacheKey
方法:分别调用原始键和签名键的updateDiskCacheKey
方法更新MessageDigest
对象,用于生成磁盘缓存键。toString
方法:返回DataCacheKey
的字符串表示,方便调试。
5.3 缓存键的使用
在 Glide 的 Engine
类中,会使用缓存键来查找和存储缓存项。例如,在 load
方法中:
java
@NonNull
public <R> EngineJob<R> load(@NonNull Context context,@NonNull Object model,@NonNull Key key,@NonNull Key signature,int width,int height,@NonNull Map<Class<?>, Transformation<?>> transformations,@NonNull Class<R> resourceClass,@NonNull Priority priority,@NonNull DiskCacheStrategy diskCacheStrategy,boolean isSkipMemoryCache,boolean onlyRetrieveFromCache,@NonNull Options options,@NonNull EngineJobListener listener) {// ...// 从活动资源缓存中查找资源EngineResource<?> active = loadFromActiveResources(key, isSkipMemoryCache);if (active != null) {listener.onEngineJobComplete(null, active);return null;}// 从内存缓存中查找资源EngineResource<?> cached = loadFromCache(key, isSkipMemoryCache);if (cached != null) {listener.onEngineJobComplete(null, cached);return null;}// ...
}
在这个方法中,使用 key
作为缓存键,分别从活动资源缓存和内存缓存中查找资源。如果找到资源,通知监听器作业完成并返回 null
。
六、缓存的清理与管理
6.1 内存缓存的清理
6.1.1 自动清理
LruResourceCache
会根据 LRU 算法自动清理最近最少使用的缓存项。当缓存大小超过最大限制时,会移除链表头部的元素,即最近最少使用的缓存项。这是通过 LruCache
内部的机制实现的,在 put
方法中会检查缓存大小是否超过最大限制,如果超过则调用 trimToSize
方法进行清理。
6.1.2 手动清理
可以通过调用 MemoryCache
的 clearMemory
方法手动清空内存缓存,或者调用 trimMemory
方法根据内存级别进行缓存清理。例如:
java
MemoryCache memoryCache = glide.getMemoryCache();
memoryCache.clearMemory(); // 清空内存缓存
memoryCache.trimMemory(android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND); // 根据内存级别清理缓存
clearMemory
方法:调用evictAll
方法清空缓存。trimMemory
方法:根据系统的内存状态进行缓存清理。当系统内存不足时,会根据不同的内存级别进行相应的清理操作。
6.2 磁盘缓存的清理
6.2.1 自动清理
DiskLruCacheWrapper
会根据 LRU 算法自动清理最近最少使用的缓存项。当磁盘缓存大小超过最大限制时,会移除最旧的缓存文件。这是通过 DiskLruCache
内部的机制实现的,在写入新的缓存项时会检查磁盘缓存大小是否超过最大限制,如果超过则移除最旧的缓存项。
6.2.2 手动清理
可以通过调用 DiskCache
的 clear
方法手动清空磁盘缓存。例如:
java
DiskCache diskCache = glide.getDiskCache();
diskCache.clear(); // 清空磁盘缓存
clear
方法会删除 DiskLruCache
中的所有缓存项,并重新打开磁盘缓存。
6.3 缓存清理的时机
6.3.1 内存不足时
在 Application
类的 onTrimMemory
方法中,可以根据内存级别调用 MemoryCache
的 trimMemory
方法进行内存缓存的清理。例如:
java
public class MyApplication extends Application {@Overridepublic void onTrimMemory(int level) {super.onTrimMemory(level);Glide.get(this).getMemoryCache().trimMemory(level);}
}
这样可以在系统内存不足时及时清理内存缓存,避免应用因内存不足而崩溃。
6.3.2 应用退出时
可以在应用退出时调用 MemoryCache
的 clearMemory
方法和 DiskCache
的 clear
方法清空所有缓存。例如:
java
@Override
public void onDestroy() {super.onDestroy();Glide.get(this).getMemoryCache().clearMemory();Glide.get(this).getDiskCache().clear();
}
这样可以释放应用占用的缓存空间,提高设备的性能。
七、缓存模块的性能优化与注意事项
7.1 性能优化建议
7.1.1 合理设置缓存大小
- 内存缓存:根据应用的实际情况合理设置内存缓存的大小。如果内存缓存设置过大,会占用过多的内存资源,可能导致应用出现内存泄漏或 OOM(Out of Memory)错误;如果设置过小,会降低缓存的命中率,增加网络请求的次数。可以通过
GlideBuilder
的setMemoryCache
方法设置内存缓存的大小。例如:
java
GlideBuilder builder = new GlideBuilder(context);
builder.setMemoryCache(new LruResourceCache(20 * 1024 * 1024)); // 设置内存缓存大小为 20MB
Glide glide = builder.build(context);
- 磁盘缓存:同样需要根据设备的存储空间和应用的需求合理设置磁盘缓存的大小。可以通过
GlideBuilder
的setDiskCache
方法设置磁盘缓存的大小。例如:
java
GlideBuilder builder = new GlideBuilder(context);
builder.setDiskCache(new ExternalCacheDiskCacheFactory(context, 50 * 1024 * 1024)); // 设置磁盘缓存大小为 50MB
Glide glide = builder.build(context);
7.1.2 选择合适的缓存策略
根据图片的使用场景和更新频率选择合适的磁盘缓存策略。例如,对于不经常更新的图片,可以使用 DiskCacheStrategy.ALL
策略,缓存原始数据和转换后的数据,以提高加载速度;对于经常更新的图片,可以使用 DiskCacheStrategy.NONE
策略,不进行磁盘缓存,避免使用旧的缓存数据。
java
Glide.with(context).load(imageUrl).diskCacheStrategy(DiskCacheStrategy.ALL).into(imageView);
7.1.3 优化缓存键的生成
确保缓存键的生成具有唯一性和稳定性。如果缓存键生成不合理,可能会导致缓存命中率降低,或者出现不同图片使用相同缓存键的情况。可以通过自定义 Key
实现类来优化缓存键的生成。
7.2 注意事项
7.2.1 缓存过期问题
虽然 Glide 的缓存机制可以提高图片加载的效率,但需要注意缓存过期的问题。对于经常更新的图片,需要及时清理缓存或使用合适的缓存策略,避免用户看到旧
接着继续分析
7.2 注意事项
7.2.1 缓存过期问题
在实际开发中,很多图片资源可能会随着时间不断更新,例如新闻图片、商品图片等。如果使用了不恰当的缓存策略,用户可能会一直看到旧的图片,影响用户体验。为了解决这个问题,可以采用以下几种方法:
- 使用签名键(Signature Key) :Glide 允许为每个请求添加一个签名键。当图片资源更新时,更改签名键的值,这样 Glide 会认为这是一个新的请求,从而重新从网络加载图片并更新缓存。例如:
java
Key signature = new ObjectKey("new_image_version_1");
Glide.with(context).load(imageUrl).signature(signature).into(imageView);
- 定期清理缓存:可以在应用中设置一个定时任务,定期清理磁盘缓存。例如,每天凌晨执行一次缓存清理操作:
java
// 假设在 Application 类中设置定时任务
public class MyApplication extends Application {@Overridepublic void onCreate() {super.onCreate();// 设置每天凌晨 2 点清理磁盘缓存AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);Intent intent = new Intent(this, CacheCleanupService.class);PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, 0);Calendar calendar = Calendar.getInstance();calendar.setTimeInMillis(System.currentTimeMillis());calendar.set(Calendar.HOUR_OF_DAY, 2);calendar.set(Calendar.MINUTE, 0);calendar.set(Calendar.SECOND, 0);if (calendar.getTimeInMillis() < System.currentTimeMillis()) {calendar.add(Calendar.DAY_OF_YEAR, 1);}if (alarmManager != null) {alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),AlarmManager.INTERVAL_DAY, pendingIntent);}}
}// 缓存清理服务
public class CacheCleanupService extends Service {@Nullable@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {Glide.get(this).clearDiskCache();stopSelf();return START_NOT_STICKY;}
}
- 使用动态缓存策略:根据图片的更新频率动态选择缓存策略。对于更新频繁的图片,使用
DiskCacheStrategy.NONE
;对于更新不频繁的图片,使用DiskCacheStrategy.ALL
。
7.2.2 缓存清理的时机
缓存清理操作可能会消耗一定的系统资源,因此需要选择合适的时机进行清理,避免在用户使用过程中进行大规模的缓存清理,影响用户体验。可以考虑以下几种时机:
- 应用处于后台时:当应用进入后台运行时,可以进行缓存清理操作。可以在
Activity
的onStop
方法中添加缓存清理逻辑,但需要注意避免在短时间内频繁清理缓存。例如:
java
@Override
protected void onStop() {super.onStop();if (isApplicationInBackground()) {Glide.get(this).clearMemory();}
}private boolean isApplicationInBackground() {ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();if (appProcesses != null) {for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {if (appProcess.processName.equals(getPackageName()) &&appProcess.importance != ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {return true;}}}return false;
}
- 设备空闲时:可以使用
JobScheduler
或WorkManager
来安排缓存清理任务,在设备空闲时执行。例如,使用WorkManager
:
java
// 定义一个缓存清理工作
public class CacheCleanupWorker extends Worker {public CacheCleanupWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {super(context, workerParams);}@NonNull@Overridepublic Result doWork() {Glide.get(getApplicationContext()).clearDiskCache();return Result.success();}
}// 安排工作
Constraints constraints = new Constraints.Builder().setRequiresDeviceIdle(true).build();
OneTimeWorkRequest cacheCleanupRequest = new OneTimeWorkRequest.Builder(CacheCleanupWorker.class).setConstraints(constraints).build();
WorkManager.getInstance(context).enqueue(cacheCleanupRequest);
7.2.3 多线程安全问题
由于缓存操作涉及到多线程访问,需要确保缓存操作的线程安全。Glide 的缓存模块已经在关键代码处使用了 synchronized
块来保证线程安全,但在自定义缓存实现时,需要特别注意线程安全问题。例如,在自定义内存缓存类时,对于共享资源的访问需要进行同步处理:
java
import java.util.HashMap;
import java.util.Map;public class CustomMemoryCache {private final Map<String, Object> cache = new HashMap<>();private final Object lock = new Object();public void put(String key, Object value) {synchronized (lock) {cache.put(key, value);}}public Object get(String key) {synchronized (lock) {return cache.get(key);}}public void remove(String key) {synchronized (lock) {cache.remove(key);}}
}
在上述代码中,使用 synchronized
块对 put
、get
和 remove
方法进行同步处理,确保在多线程环境下对 cache
这个共享资源的访问是线程安全的。
八、缓存模块与其他模块的协作
8.1 与网络模块的协作
Glide 的缓存模块与网络模块紧密协作,当缓存中没有所需的图片资源时,会触发网络请求。在 Engine
类的 load
方法中,当内存缓存和磁盘缓存都没有找到资源时,会创建 SourceGenerator
来进行源数据的加载,通常会发起网络请求。例如:
java
if (diskCacheStrategy.isCacheSource()) {// 尝试从磁盘缓存加载EngineJob<R> diskCacheJob = ...;diskCacheJob.start(new DiskCacheGenerator(...));
} else {// 直接从源数据加载,可能是网络请求EngineJob<R> sourceJob = ...;sourceJob.start(new SourceGenerator(...));
}
SourceGenerator
会根据不同的数据源(如网络、文件等)进行相应的加载操作。如果是网络请求,会使用 Glide 的网络模块(如 HttpUrlFetcher
)来获取图片数据。当网络请求成功后,会将图片数据存储到磁盘缓存和内存缓存中,以便下次使用。
8.2 与转换模块的协作
Glide 的转换模块用于对图片进行各种处理,如缩放、裁剪、圆角处理等。缓存模块与转换模块的协作体现在缓存的存储和读取上。当图片经过转换后,会生成一个新的资源,这个资源会被存储到磁盘缓存和内存缓存中。在 DiskCacheStrategy
中,RESOURCE
策略表示只缓存转换后的数据。例如:
java
Glide.with(context).load(imageUrl).transform(new CircleCrop()) // 进行圆形裁剪转换.diskCacheStrategy(DiskCacheStrategy.RESOURCE).into(imageView);
当再次请求相同的图片并进行相同的转换时,Glide 会直接从缓存中读取转换后的资源,避免重复的转换操作,提高加载效率。
8.3 与生命周期管理模块的协作
Glide 的生命周期管理模块用于管理图片加载请求的生命周期,确保在 Activity
或 Fragment
销毁时,取消未完成的图片加载请求,避免内存泄漏。缓存模块与生命周期管理模块的协作体现在资源的释放上。当 Activity
或 Fragment
销毁时,会调用 Glide.with(this).clear()
方法,该方法会释放活动资源缓存中的资源,并进行相应的清理操作。例如:
java
@Override
protected void onDestroy() {super.onDestroy();Glide.with(this).clear();
}
这样可以确保在 Activity
或 Fragment
销毁时,及时释放缓存资源,避免内存泄漏。
九、自定义缓存实现
9.1 自定义内存缓存
如果默认的 LruResourceCache
不能满足需求,可以自定义内存缓存。需要实现 MemoryCache
接口,并根据自己的需求实现相应的方法。例如:
java
import com.bumptech.glide.load.Key;
import com.bumptech.glide.load.engine.Resource;
import com.bumptech.glide.load.engine.cache.MemoryCache;import java.util.HashMap;
import java.util.Map;public class CustomMemoryCache implements MemoryCache {private final Map<Key, Resource<?>> cache = new HashMap<>();private ResourceRemovedListener listener;@Overridepublic void setResourceRemovedListener(ResourceRemovedListener listener) {this.listener = listener;}@Overridepublic Resource<?> put(Key key, Resource<?> resource) {if (key == null || resource == null) {return null;}Resource<?> previous = cache.put(key, resource);if (previous != null && listener != null) {listener.onResourceRemoved(previous);}return previous;}@Overridepublic Resource<?> get(Key key) {if (key == null) {return null;}return cache.get(key);}@Overridepublic Resource<?> remove(Key key) {if (key == null) {return null;}Resource<?> removed = cache.remove(key);if (removed != null && listener != null) {listener.onResourceRemoved(removed);}return removed;}@Overridepublic void clearMemory() {for (Map.Entry<Key, Resource<?>> entry : cache.entrySet()) {Resource<?> resource = entry.getValue();if (resource != null && listener != null) {listener.onResourceRemoved(resource);}}cache.clear();}@Overridepublic void trimMemory(int level) {// 根据内存级别进行缓存清理if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {clearMemory();} else if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN|| level == android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {// 可以实现更复杂的清理逻辑,如移除部分缓存项int size = cache.size();int toRemove = size / 2;int removedCount = 0;for (Map.Entry<Key, Resource<?>> entry : cache.entrySet()) {if (removedCount >= toRemove) {break;}Resource<?> resource = entry.getValue();if (resource != null && listener != null) {listener.onResourceRemoved(resource);}cache.remove(entry.getKey());removedCount++;}}}
}
在自定义内存缓存中,使用 HashMap
来存储缓存项,并实现了 put
、get
、remove
、clearMemory
和 trimMemory
等方法。在 put
和 remove
方法中,会调用 ResourceRemovedListener
的 onResourceRemoved
方法通知资源移除事件。
9.2 自定义磁盘缓存
如果需要自定义磁盘缓存,可以实现 DiskCache
接口。例如:
java
import com.bumptech.glide.load.Key;
import com.bumptech.glide.load.engine.cache.DiskCache;import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;public class CustomDiskCache implements DiskCache {private final File cacheDirectory;public CustomDiskCache(File cacheDirectory) {this.cacheDirectory = cacheDirectory;}@Overridepublic InputStream get(Key key) {File file = getFileForKey(key);if (file.exists()) {try {// 实现文件读取逻辑// 这里只是简单示例,实际需要处理文件读取异常return new java.io.FileInputStream(file);} catch (IOException e) {e.printStackTrace();}}return null;}@Overridepublic void put(Key key, Writer writer) {File file = getFileForKey(key);try {if (!file.getParentFile().exists()) {file.getParentFile().mkdirs();}if (file.createNewFile()) {try (OutputStream outputStream = new java.io.FileOutputStream(file)) {writer.write(outputStream);}}} catch (IOException e) {e.printStackTrace();}}@Overridepublic void delete(Key key) {File file = getFileForKey(key);if (file.exists()) {file.delete();}}@Overridepublic void clear() {if (cacheDirectory.exists()) {deleteDirectory(cacheDirectory);}}private File getFileForKey(Key key) {String fileName = key.toString();return new File(cacheDirectory, fileName);}private void deleteDirectory(File directory) {if (directory.isDirectory()) {File[] files = directory.listFiles();if (files != null) {for (File file : files) {if (file.isDirectory()) {deleteDirectory(file);} else {file.delete();}}}directory.delete();}}
}
在自定义磁盘缓存中,使用 File
类来管理磁盘文件。实现了 get
、put
、delete
和 clear
等方法,用于读取、写入、删除和清空磁盘缓存。
十、总结
Glide 的缓存模块是一个高度复杂且强大的系统,通过内存缓存和磁盘缓存的协同工作,显著提升了图片加载的效率和性能。内存缓存采用 LRU 算法,能够快速响应图片请求,减少用户等待时间;磁盘缓存则基于 DiskLruCache 实现,有效地持久化图片资源,降低网络流量消耗。
缓存键的精心设计确保了缓存的唯一性和一致性,使得相同的图片资源能够被准确地缓存和复用。同时,缓存的清理与管理机制保证了缓存的有效性和资源的合理利用,避免了缓存数据的过期和占用过多的系统资源。
在实际开发中,开发者需要根据应用的具体需求,合理设置缓存大小和选择合适的缓存策略。同时,要注意缓存过期、清理时机和多线程安全等问题,以确保应用的稳定性和性能。此外,Glide 提供了丰富的扩展接口,允许开发者自定义缓存实现,以满足特殊的业务需求。
随着 Android 技术的不断发展和应用场景的日益复杂,Glide 的缓存模块也将不断优化和完善,为开发者提供更加高效、灵活的图片缓存解决方案,助力打造更加优质的 Android 应用。
相关文章:
Android Glide 缓存模块源码深度解析
一、引言 在 Android 开发领域,图片加载是一个极为常见且关键的功能。Glide 作为一款被广泛使用的图片加载库,其缓存模块是提升图片加载效率和性能的核心组件。合理的缓存机制能够显著减少网络请求,降低流量消耗,同时加快图片显示…...
蓝桥杯备赛:炮弹
题目解析 这道题目是一道模拟加调和级数,难的就是调和级数,模拟过程比较简单。 做法 这道题目的难点在于我们在玩这个跳的过程,可能出现来回跳的情况,那么为了解决这种情况,我们采取的方法是设定其的上限步数。那么…...
死锁问题分析工具
使用 gdb 调试 gdb ./your_program (gdb) run (gdb) thread apply all bt还可以分析pthread_mutex内部,查看owen字段分析哪个线程占用的锁,一个可能的 pthread_mutex 内部结构可以大致表示为: typedef struct pthread_mutex_t {int state; …...
装饰器模式--RequestWrapper、请求流request无法被重复读取
目录 前言一、场景二、原因分析三、解决四、更多 前言 曾经遇见这么一段代码,能看出来是把request又重新包装了一下,核心信息都不会改变 后面了解到这叫 装饰器模式(Decorator Pattern) :也称为包装模式(Wrapper Pat…...
MTK Android12 桌面上显示文件管理器图标
文章目录 需求解决 需求 在MTK平台上,Android12的文件管理器图标未显示在桌面,但在设置里面可以看到,文件管理器是安装的。根据客户要求,需要将文件管理器的图标显示在桌面上。解决 路径:packages/apps/DocumentsUI/…...
SpringBoot实现文件上传
1. 配置文件上传限制 application.yml spring:servlet:multipart:max-file-size: 10MBmax-request-size: 10MB2. 创建文件上传控制器 import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import…...
【开源免费】基于SpringBoot+Vue.JS青年公寓服务平台(JAVA毕业设计)
本文项目编号 T 233 ,文末自助获取源码 \color{red}{T233,文末自助获取源码} T233,文末自助获取源码 目录 一、系统介绍二、数据库设计三、配套教程3.1 启动教程3.2 讲解视频3.3 二次开发教程 四、功能截图五、文案资料5.1 选题背景5.2 国内…...
django中视图作用和视图功能 以及用法
在 Django REST Framework(DRF)中,视图(View)是处理 HTTP 请求并返回响应的核心组件。DRF 提供了多种视图类,适用于不同的场景和需求。以下是 DRF 中常见的视图类及其作用、使用方法的详细说明: 一、DRF 视图的分类 DRF 的视图可以分为以下几类: 基于函数的视图(Func…...
大语言模型在患者交互任务中的临床使用评估框架
An evaluation framework for clinical use of large language models in patient interaction tasks An evaluation framework for clinical use of large language models in patient interaction tasks | Nature Medicine 2025.1 收到时间:2023 年 8 月 8 日 …...
Python—类class复习
Python——类(class)复习 根据类来创建对象的方法被称为实例化 因此学会使用类(class)来进行编程就是初步进入面向对象编程的大门 1.1 创建和使用类 首先编写一个小狗的简单类Dog,它表示的不是特定的小狗ÿ…...
QT | 信号与槽(超详解)
前言 对qt信号和槽的详细解释 💓 个人主页:普通young man-CSDN博客 ⏩ 文章专栏:C_普通young man的博客-CSDN博客 ⏩ 本人giee: 普通小青年 (pu-tong-young-man) - Gitee.com 若有问题 评论区见📝 🎉欢迎大家点赞&am…...
Codecraft-17 and Codeforces Round 391 E. Bash Plays with Functions 积性函数
题目链接 题目大意 定义函数 f r ( n ) f_r(n) fr(n) : 在 r 0 r0 r0时,为满足 p p p ⋅ \cdot ⋅ q n qn qn , 且 g c d ( p , q ) 1 gcd(p,q)1 gcd(p,q)1 的有序对 ( p , q ) (p,q) (p,q) 个数;在 r r r ≥ \geq ≥ 1 1 1时࿰…...
粉尘环境下的智能生产革命 ——助力矿山行业实现高效自动化作业
在矿山开采领域,运输系统是保障生产连续性的核心环节。然而,粉尘弥漫、环境恶劣、设备分散等问题,长期制约着矿山运输的效率与安全性。传统的集中式控制系统难以适应复杂工况,而远程分布式 IO 模块与 PLC 的深度融合,正…...
更新vscode ,将c++11更新到c++20
要在CentOS系统中安装最新版本的GCC,你可以使用SCL(Software Collections)仓库,它提供了开发工具的最新版本。以下是安装步骤: 1、 添加SCL仓库: 首先,添加CentOS的SCL仓库,该仓库…...
Numpy实训:读取并分析iris数据集中鸢尾花的相关数据
实训中相关数据集,请联系博主邮箱"1438077481qq.com",在邮箱内发送"iris.csv"即可快速获取,无任何套路,秉承开源精神! 1、导入模块 #导入模块 import numpy as np import csv 2、获取数据 iri…...
nats jetstream server code 分析
对象和缩写 jetstream导入两个对象:stream and consumer,在stream 之上构造jetstreamapi。在nats代码中,以下是一些常见的缩写 1.mset is stream 2.jsX is something of jetstream 3.o is consumer 代码分析 对于producer ,发送…...
德鲁伊连接池
德鲁伊连接池(Druid Connection Pool)是一个开源的Java数据库连接池项目,用于提高数据库连接的性能和可靠性。德鲁伊连接池通过复用数据库连接、定时验证连接的可用性、自动回收空闲连接等机制,有效减少了数据库连接的创建和销毁开…...
Python从入门到精通1:FastAPI
引言 在现代 Web 开发中,API 是前后端分离架构的核心。FastAPI 凭借其高性能、简洁的语法和自动文档生成功能,成为 Python 开发者的首选框架。本文将从零开始,详细讲解 FastAPI 的核心概念、安装配置、路由设计、请求处理以及实际应用案例&a…...
C语言经典案例-菜鸟经典案例
1.输入某年某月某日,判断这一天是这一年的第几天? //输入某年某月某日,判断这一天是这一年的第几天? #include <stdio.h>int isLeapYear(int year) {// 闰年的判断规则:能被4整除且(不能被100整除或…...
SpringBoot过滤器(Filter)的使用:Filter接口、FilterRegistrationBean类配置、@WebFilter注释
1、过滤器(Filter)的介绍 Spring Boot 的过滤器用于对数据进行过滤处理。通过 Spring Boot 的过滤器,程序开发人员不仅可以对用户通过 URL 地址发送的请求进行过滤处理(例如:过滤一些错误的请求或者请求中的敏感词等),而且可以对服务器返回的数据进行过滤处理(例如:压…...
采用内存局部性分配有什么好处?
内存分配时的局部性分配(Locality of Allocation)是指将相关的内存对象分配在相邻或相近的内存区域中。这种分配策略在现代计算机系统中具有显著的好处,主要体现在以下几个方面: 1. 提高缓存命中率 现代计算机系统依赖于多级缓存…...
一周热点-OpenAI 推出了 GPT-4.5,这可能是其最后一个非推理模型
在人工智能领域,大型语言模型一直是研究的热点。OpenAI 的 GPT 系列模型在自然语言处理方面取得了显著成就。GPT-4.5 是 OpenAI 在这一领域的又一力作,它在多个方面进行了升级和优化。 1 新模型的出现 GPT-4.5 目前作为研究预览版发布。与 OpenAI 最近的 o1 和 o3 模型不同,…...
分布式ETCD面试题及参考答案
目录 ETCD 适用的六大场景及其实现原理 ETCD 与 Redis 在分布式锁实现上的差异 解释 ETCD 的 Watch 机制及其应用场景 ETCD 如何实现服务发现?与 ZooKeeper 有何不同? ETCD 实现服务发现的方式 与 ZooKeeper 的不同 ETCD 的键值存储模型支持哪些操作? 为什么 ETCD 适…...
MySQL进阶-关联查询优化
采用左外连接 下面开始 EXPLAIN 分析 EXPLAIN SELECT SQL_NO_CACHE * FROM type LEFT JOIN book ON type.card book.card; 结论:type 有All ,代表着全表扫描,效率较差 添加索引优化 ALTER TABLE book ADD INDEX Y ( card); #【被驱动表】࿰…...
ESP32驱动OV3660摄像头实现EdgeImpulse图像识别(摄像头支持红外夜视、边缘AI计算)
目录 1、传感器特性 2、硬件原理图 3、驱动程序 ESP32-S3 AI智能摄像头模块是一款专为智能家居和物联网应用打造的高性能边缘AI开发模组。它集成了摄像头、麦克风、音频功放、环境光传感器和夜视补光灯,无需依赖云端即可实现本地化AI推理。 凭借TensorFlow Lite、YOLO和O…...
SpringSecurity认证授权完整流程
SpringSecurity认证流程:loadUserByUsername()方法内部实现。 实现步骤: 构建一个自定义的service接口,实现SpringSecurity的UserDetailService接口。建一个service实现类,实现此loadUserByUsername方法。…...
java_了解反射机制
目录 1. 定义 2. 用途 3. 反射基本信息 4. 反射相关的类 4.1 class类(反射机制的起源) 4.1.1 Class类中的相关方法(方法的具体使用在后面的示例中) 4.2 反射的示例 4.2.1 获得Class对象的三种方式 4.2.2 反射的使用 Fiel…...
【赵渝强老师】管理MongoDB的运行
MongoDB提供了mongod命令用于启动MongoDB服务器端;而停止MongoDB服务器却可以通过几种不同的方式完成。下面分别进行介绍。 一、【实战】启动MongoDB服务器 通过执行下面的语句可以查看启动MongoDB服务器的帮助信息: mongod --help# 输出的信息如下&a…...
【学习思维模型】
学习思维模型 一、理解类模型二、记忆类模型三、解决问题类模型四、结构化学习模型五、效率与习惯类模型六、高阶思维模型七、实践建议八、新增学习思维模型**1. 波利亚问题解决四步法****2. 主动回忆(Active Recall)****3. 鱼骨图(因果图/Ishikawa Diagram)****4. MECE原则…...
阿里发布新开源视频生成模型Wan-Video,支持文生图和图生图,最低6G就能跑,ComFyUI可用!
Wan-Video 模型介绍:包括 Wan-Video-1.3B-T2V 和 Wan-Video-14B-T2V 两个版本,分别支持文本到视频(T2V)和图像到视频(I2V)生成。14B 版本需要更高的 VRAM 配置。 Wan2.1 是一套全面开放的视频基础模型&…...
安孚科技携手政府产业基金、高能时代发力固态电池,开辟南孚电池发展新赛道
安孚科技出手,发力固态电池。 3月7日晚间,安孚科技(603031.SH)发布公告称,公司控股子公司南孚电池拟与南平市绿色产业投资基金有限公司(下称“南平绿色产业基金”)、高能时代(广东横…...
moodle 开源的在线学习管理系统(LMS)部署
一、Moodle 简介 Moodle(Modular Object-Oriented Dynamic Learning Environment)是一个开源的在线学习管理系统(LMS),广泛应用于教育机构和企业培训。其核心功能包括课程管理、作业提交、在线测试、论坛互动和成绩跟…...
设备树的概念
可以理解为设备树的树干是系统总线,树枝上面是其他的不同的通信协议线。对于不同通信协议的设备挂载在对应的节点即可 在设备树出现以前,所有关于设备的具体信息都要写在驱动里,一旦外围设备变化,驱动代码就要重写。 引入了设…...
【ArcGIS】地理坐标系
文章目录 一、坐标系理论体系深度解析1.1 地球形态的数学表达演进史1.1.1 地球曲率的认知变化1.1.2 参考椭球体参数对比表 1.2 地理坐标系的三维密码1.2.1 经纬度的本质1.2.2 大地基准面(Datum)的奥秘 1.3 投影坐标系:平面世界的诞生1.3.1 投…...
MATLAB控制函数测试要点剖析
一、功能准确性检验 基础功能核验 针对常用控制函数,像用于传递函数建模的 tf 、构建状态空间模型的 ss ,以及开展阶跃响应分析的 step 等,必须确认其能精准执行基础操作。以 tf 函数为例,在输入分子与分母系数后,理…...
如何让一个类作为可调用对象被thread调用?
如何让一个类作为可调用对象,被 std::thread 调用 在 C 中,可以让一个类对象作为可调用对象(Callable Object),然后用 std::thread 进行调用。要实现这一点,主要有三种方法: 重载 operator()&…...
OpenWrt 串口终端常用命令---拓展篇
以下进一步拓展 OpenWrt 串口终端常用命令,新增更多高级操作与场景化工具,助你深入掌握系统管理与调试技巧: 一、系统信息与状态查询(扩展) 硬件详细探测 cat /proc/mtd # 查看 Flash 分区表(MTD 设备) mtd info # 显示 MTD 分…...
线上接口tp99突然升高如何排查?
当线上接口的 TP99 突然升高时,意味着该接口在 99% 的情况下响应时间变长,这可能会严重影响系统的性能和用户体验。可以按照下面的步骤进行排查。这里我们先说明一下如何计算tp99:监控系统计算 TP99(第 99 百分位数的响应时间&…...
如何借助人工智能AI模型开发一个类似OpenAI Operator的智能体实现电脑自动化操作?
这几天关于Manus的新闻铺天盖地,于是研究了一下AI智能体的实现思路,发现Openai 的OpenAI Operator智能体已经实现了很强的功能,但是每月200美金的价格高不可攀,而Manus的邀请码据说炒到了几万块!就想能不能求助人工智能…...
langchain系列(终)- LangGraph 多智能体详解
目录 一、导读 二、概念原理 1、智能体 2、多智能体 3、智能体弊端 4、多智能体优点 5、多智能体架构 6、交接(Handoffs) 7、架构说明 (1)网络 (2)监督者 (3)监督者&…...
springboot旅游管理系统设计与实现(代码+数据库+LW)
摘 要 现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本旅游管理系统就是在这样的大环境下诞生,其可以帮助使用者在短时间内处理完毕庞大的数据信息&a…...
【前端跨域】WebSocket如何实现跨域通信?原理、实践与安全指南
在实时通信场景(如在线聊天、实时数据推送)中,WebSocket因其高效的双向通信能力成为首选技术 然而,当客户端与服务器部署在不同源时,跨域问题同样可能阻碍WebSocket的连接 一、WebSocket与跨域的关系 WebSocket的跨…...
Go红队开发—格式导出
文章目录 输出功能CSV输出CSV 转 结构体结构体 转 CSV端口扫描结果使用CSV格式导出 HTML输出Sqlite输出nmap扫描 JSONmap转json结构体转jsonjson写入文件json编解码json转结构体json转mapjson转string练习:nmap扫描结果导出json格式 输出功能 在我们使用安全工具的…...
Sharp 存在任意文件读取漏洞( DVB-2025-8923)
免责声明 本文所描述的漏洞及其复现步骤仅供网络安全研究与教育目的使用。任何人不得将本文提供的信息用于非法目的或未经授权的系统测试。作者不对任何由于使用本文信息而导致的直接或间接损害承担责任。如涉及侵权,请及时与我们联系,我们将尽快处理并删除相关内容。 0x01…...
C++数组,链表,二叉树的内存排列是什么样的,结构体占多大内存如何计算,类占多大内存如何计算,空类的空间是多少,为什么?
C数组是连续存储的,C数组元素依次存放在相邻的内存地址之中,并且内存大小相同。 C链表是离散存储的,C链表是由节点构成的,每个节点之中存在节点的值以及指向下一个节点的指针,每个节点是动态分配的。 C二叉树也是离散…...
【vLLM 教程】使用 TPU 安装
vLLM 是一款专为大语言模型推理加速而设计的框架,实现了 KV 缓存内存几乎零浪费,解决了内存管理瓶颈问题。 更多 vLLM 中文文档及教程可访问 →https://vllm.hyper.ai/ vLLM 使用 PyTorch XLA 支持 Google Cloud TPU。 依赖环境 Google Cloud TPU …...
【RAG】基于向量检索的 RAG (BGE示例)
RAG机器人 结构体 文本向量化: 使用 BGE 模型将文档和查询编码为向量。 (BGE 是专为检索任务优化的开源 Embedding 模型,除了本文API调用,也可以通过Hugging Face 本地部署BGE 开源模型) 向量检索: 从数据库中找到与查询相关的文…...
【RAG】RAG 系统的基本搭建流程(ES关键词检索示例)
RAG 系统的基本搭建流程 搭建过程: 文档加载,并按一定条件切割成片段将切割的文本片段灌入检索引擎封装检索接口构建调用流程:Query -> 检索 -> Prompt -> LLM -> 回复 1. 文档的加载与切割 # !pip install --upgrade openai…...
PSIM积累经验
1、三极管的部署报错。 出错信息: 元件: R 名称: R2 Error: The RLC branch R2 is connected to the gate node of the switch Q1. The gate node should be connected to an On-Off Controller output. Refer to the switch Help p…...
C++之vector类(超详解)
这节我们来学习一下,C中一个重要的工具——STL,这是C中自带的一个标准库,我们可以直接调用这个库中的函数或者容器,可以使效率大大提升。这节我们介绍STL中的vector。 文章目录 前言 一、标准库类型vector 二、vector的使用 2.…...