结合 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
或其他高效网络库,优化连接复用。 - 配置合理的超时和重试策略。
- 集成