结合 Glide 4.x 的源码,详细讲解 Glide 三级缓存模块(内存缓存、磁盘缓存和网络加载)的实现细节。Glide 的缓存机制是其高效性和低内存占用的关键,通过内存缓存(LruResourceCache 和 ActiveResources)、磁盘缓存(DiskLruCache)以及网络加载的协同工作,实现了快速的图片加载和资源管理。以下是每个缓存模块的详细分析,包括源码、设计原理和优化策略。
1. 内存缓存
内存缓存是 Glide 的第一级缓存,用于存储已加载并解码的图片资源(如 Bitmap 或 Drawable),以实现快速访问。内存缓存由两部分组成:LruResourceCache 和 ActiveResources。
1.1 LruResourceCache
LruResourceCache 是基于 LRU(Least Recently Used)算法的内存缓存,存储未被当前 Target 使用的资源。
源码分析:LruResourceCache
java
public class LruResourceCache extends LruCache<Key, Resource<?>> {
private final ResourceRemovedListener listener;
public LruResourceCache(long size) {
super(size);
this.listener = resource -> resource.recycle();
}
@Override
protected void onItemEvicted(@NonNull Key key, @Nullable Resource<?> item) {
if (item != null) {
listener.onResourceRemoved(item);
}
}
}
- 作用 :存储加载完成的资源,基于内存大小限制(
size)进行 LRU 淘汰。 - 实现细节 :
LruResourceCache继承自LruCache,使用键值对(Key->Resource)存储资源。Key是一个EngineKey实例,包含图片的 URI、尺寸、变换等信息,确保缓存的唯一性。- 当缓存超出大小限制时,调用
onItemEvicted,触发Resource.recycle(),释放资源(如回收Bitmap到BitmapPool)。 size默认基于设备可用内存计算(通常为可用内存的 1/8,参考MemorySizeCalculator)。
关键点:
LruResourceCache只存储未被Target引用的资源,一旦资源被Target使用,会转移到ActiveResources。- 缓存清理通过
onTrimMemory或onLowMemory响应系统内存压力。
1.2 ActiveResources
ActiveResources 是内存缓存的另一部分,存储当前正在被 Target 使用的资源,防止被垃圾回收。
源码分析:ActiveResources
java
final class ActiveResources {
private final Map<Key, ResourceWeakReference> activeResources = new HashMap<>();
private final ReferenceQueue<Resource<?>> referenceQueue = new ReferenceQueue<>();
@Nullable
Resource<?> get(Key key) {
ResourceWeakReference ref = activeResources.get(key);
if (ref != null) {
Resource<?> resource = ref.get();
if (resource == null) {
activeResources.remove(key);
}
return resource;
}
return null;
}
void deactivate(Key key) {
ResourceWeakReference removed = activeResources.remove(key);
if (removed != null) {
Resource<?> resource = removed.get();
if (resource != null) {
listener.onResourceReleased(key, resource);
}
}
}
}
- 作用 :存储被
Target使用的资源,使用弱引用(WeakReference)管理,允许 GC 回收未使用的资源。 - 实现细节 :
activeResources是一个HashMap,键为Key,值为ResourceWeakReference(包装了Resource的弱引用)。- 当
Target使用资源时,资源从LruResourceCache移到ActiveResources。 - 当资源不再被使用(
Target调用clear),deactivate将资源移回LruResourceCache或回收。 ReferenceQueue用于监控弱引用被 GC 回收的情况,自动清理无效条目。
关键点:
ActiveResources使用弱引用避免内存泄漏,当ImageView或Target被销毁时,资源可被 GC 回收。- 资源从
ActiveResources移回LruResourceCache时,会重新进入 LRU 管理。
内存缓存流程
- 检查缓存 :
Engine.load()首先调用memoryCache.get(key)检查内存缓存。 - 命中 ActiveResources :如果资源在
ActiveResources中,直接返回。 - 命中 LruResourceCache :如果在
LruResourceCache中,移到ActiveResources并返回。 - 未命中:继续检查磁盘缓存或网络加载。
- 存储 :加载完成的新资源存入
LruResourceCache,使用时移到ActiveResources。
优化策略:
-
BitmapPool :Glide 使用
LruBitmapPool复用Bitmap,减少内存分配。被回收的Bitmap会进入BitmapPool。javapublic class LruBitmapPool implements BitmapPool { private final LruPoolStrategy strategy; @Override public void put(Bitmap bitmap) { if (!bitmap.isMutable()) { bitmap.recycle(); return; } strategy.put(bitmap); } } -
动态内存管理 :通过
MemorySizeCalculator根据设备内存动态调整缓存大小。 -
弱引用 :
ActiveResources使用弱引用,降低内存泄漏风险。
2. 磁盘缓存
磁盘缓存是 Glide 的第二级缓存,用于存储图片的原始数据或转换后的资源。Glide 使用 DiskLruCache 实现磁盘缓存,支持多种缓存策略(如 DiskCacheStrategy.ALL)。
源码分析:DiskLruCache
java
public class DiskLruCache implements DiskCache {
private final File directory;
private final DiskCacheWriteLocker writeLocker = new DiskCacheWriteLocker();
private final DiskLruCacheWrapper cache;
public static DiskCache get(File directory, long maxSize) {
return new DiskLruCache(directory, maxSize);
}
@Override
public void put(Key key, Writer writer) {
String safeKey = safeKeyGenerator.getSafeKey(key);
writeLocker.acquire(safeKey);
try {
cache.put(safeKey, writer);
} finally {
writeLocker.release(safeKey);
}
}
@Override
public File get(Key key) {
String safeKey = safeKeyGenerator.getSafeKey(key);
return cache.get(safeKey);
}
}
- 作用:存储图片数据到磁盘,基于 LRU 算法管理缓存文件。
- 实现细节 :
directory是缓存目录(默认在应用的缓存目录下)。maxSize是磁盘缓存的最大大小(默认 250MB,可通过GlideBuilder配置)。safeKeyGenerator将Key(包含 URI、变换等)转换为安全的文件名(通过 SHA-256 哈希)。DiskCacheWriteLocker确保线程安全的文件写入。
缓存策略:DiskCacheStrategy
DiskCacheStrategy 控制缓存行为:
DiskCacheStrategy.ALL:缓存原始数据和转换后的资源。DiskCacheStrategy.DATA:仅缓存原始数据。DiskCacheStrategy.RESOURCE:仅缓存转换后的资源。DiskCacheStrategy.NONE:不使用磁盘缓存。DiskCacheStrategy.AUTOMATIC(默认):根据数据源和编码策略智能选择。
磁盘缓存流程
- 检查缓存 :
DecodeJob调用diskCache.get(key)检查磁盘缓存。 - 命中 :读取缓存文件,解码为
Resource(如Bitmap)。 - 未命中:从网络加载数据。
- 存储 :加载完成的数据通过
diskCache.put(key, writer)写入缓存。
源码分析:DecodeJob.decodeFromCache
java
private Resource<?> decodeFromCache() throws Exception {
Resource<?> result = null;
if (diskCacheStrategy.isResourceCacheable()) {
result = decodeFromResourceCache();
}
if (result == null && diskCacheStrategy.isDataCacheable()) {
result = decodeFromDataCache();
}
return result;
}
- 作用 :根据
DiskCacheStrategy,优先尝试加载转换后的资源(RESOURCE),再尝试原始数据(DATA)。 - 实现细节 :
decodeFromResourceCache:加载转换后的资源(如已裁剪的Bitmap)。decodeFromDataCache:加载原始数据(如 JPEG 文件),然后解码和转换。
优化策略:
- 线程安全 :
DiskCacheWriteLocker使用锁机制,防止多线程写入冲突。 - 文件命名:通过 SHA-256 哈希生成唯一文件名,避免非法字符问题。
- 异步写入:磁盘写入在后台线程执行,不阻塞主线程。
- 缓存清理 :当缓存超过
maxSize时,DiskLruCache自动删除最久未使用的文件。
3. 网络加载
网络加载是 Glide 的第三级缓存,当内存和磁盘缓存都未命中时,Glide 通过 DataFetcher 从网络获取数据。
源码分析:HttpUrlFetcher
java
public class HttpUrlFetcher implements DataFetcher<InputStream> {
private final GlideUrl glideUrl;
private final int timeout;
@Override
public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
HttpURLConnection urlConnection = null;
try {
urlConnection = buildAndConfigureConnection();
InputStream result = urlConnection.getInputStream();
callback.onDataReady(result);
} catch (IOException e) {
callback.onLoadFailed(e);
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
}
}
- 作用:通过 HTTP 请求获取图片数据流。
- 实现细节 :
GlideUrl封装了图片的 URL 和请求头。buildAndConfigureConnection配置连接参数(如超时、缓存控制)。- 数据以
InputStream形式返回,交给解码器处理。
网络加载流程
- 触发 :
DecodeJob调用DataFetcher.loadData()发起网络请求。 - 数据获取 :
HttpUrlFetcher使用HttpURLConnection下载数据。 - 解码 :数据流通过
StreamDecoder(如StreamBitmapDecoder)解码为Bitmap或其他资源。 - 缓存 :原始数据或转换后的资源根据
DiskCacheStrategy存入磁盘缓存。
优化策略:
- 连接复用 :Glide 使用
OkHttp(通过OkHttpIntegration)或自定义HttpUrlConnection优化网络性能。 - 异步执行 :网络请求在
sourceExecutor线程池中运行,避免阻塞。 - 错误重试 :
HttpUrlFetcher支持超时和重试机制,提高成功率。
三级缓存协同工作
- 优先级 :
- 内存缓存(
LruResourceCache和ActiveResources):最快,优先检查。 - 磁盘缓存(
DiskLruCache):次快,检查原始数据或转换资源。 - 网络加载(
HttpUrlFetcher):最慢,仅在缓存未命中时触发。
- 内存缓存(
- 流程 :
Engine.load()按顺序检查memoryCache、diskCache,最后发起网络请求。- 加载完成的数据根据配置存入内存和磁盘缓存。
- 缓存键 :
- 所有缓存使用统一的
Key(EngineKey或DataCacheKey),包含 URI、尺寸、变换等,确保缓存一致性。
- 所有缓存使用统一的
关键设计亮点
- 模块化 :
- 缓存模块通过接口(
MemoryCache、DiskCache、DataFetcher)解耦,支持自定义实现。
- 缓存模块通过接口(
- 高效性 :
- 内存缓存使用 LRU 和弱引用,平衡速度和内存占用。
- 磁盘缓存通过
DiskLruCache实现高效的文件管理。 - 网络加载支持连接复用和异步执行。
- 灵活性 :
DiskCacheStrategy提供多种缓存选项,满足不同场景。BitmapPool复用Bitmap,减少 GC 压力。
- 线程安全 :
- 内存缓存使用同步机制,磁盘缓存使用锁,网络加载在独立线程运行。
源码中的典型调用链
以下是三级缓存的典型调用链:
java
// Engine.load() 检查内存缓存
EngineResource<?> cached = memoryCache.get(key);
if (cached != null) {
callback.onResourceReady(cached);
return;
}
// DecodeJob.run() 检查磁盘缓存
Resource<?> resource = decodeFromCache();
if (resource != null) {
return resource;
}
// HttpUrlFetcher.loadData() 发起网络请求
InputStream data = urlConnection.getInputStream();
resource = decoder.decode(data, width, height);
// 存储到缓存
memoryCache.put(key, resource);
diskCache.put(key, writer);
常见问题与优化建议
- 内存缓存占用过多 :
- 调整
MemorySizeCalculator的缓存大小,或通过GlideBuilder设置更小的缓存。 - 增加
BitmapPool使用率,减少新Bitmap分配。
- 调整
- 磁盘缓存命中率低 :
- 检查
DiskCacheStrategy配置,确保缓存原始数据和转换资源。 - 使用一致的
Key(避免频繁变换导致缓存失效)。
- 检查
- 网络加载慢 :
- 集成
OkHttp或其他高效网络库,优化连接复用。 - 配置合理的超时和重试策略。
- 集成