当前位置: 首页 > article >正文

Android Glide 缓存模块源码深度解析

一、引言

在 Android 开发领域,图片加载是一个极为常见且关键的功能。Glide 作为一款被广泛使用的图片加载库,其缓存模块是提升图片加载效率和性能的核心组件。合理的缓存机制能够显著减少网络请求,降低流量消耗,同时加快图片显示速度,为用户带来流畅的使用体验。本文将深入 Glide 缓存模块的源码,从整体架构到具体实现细节,全方位剖析其工作原理。

二、Glide 缓存模块概述

2.1 缓存的重要性

在移动应用中,图片资源通常占据较大的存储空间和网络带宽。频繁的网络请求不仅会增加用户的流量成本,还会导致图片加载缓慢,影响用户体验。通过缓存机制,Glide 可以将已经加载过的图片存储在本地,下次需要加载相同图片时,直接从缓存中获取,从而避免重复的网络请求,提高加载速度。

2.2 缓存的类型

Glide 提供了两种主要的缓存类型:内存缓存和磁盘缓存。

  • 内存缓存:将图片存储在内存中,读取速度极快,能够实现图片的快速显示。Glide 默认使用 LRU(Least Recently Used,最近最少使用)算法来管理内存缓存,确保在内存有限的情况下,优先保留最近使用的图片。
  • 磁盘缓存:将图片存储在设备的磁盘上,适用于长期保存图片。Glide 使用 DiskLruCache 来实现磁盘缓存,同样采用 LRU 算法进行管理,保证磁盘空间的合理利用。

2.3 缓存的级别

Glide 的缓存分为多个级别,按照查找顺序依次为:

  1. 活动资源缓存(Active Resources) :存储正在被使用的图片资源,避免重复加载。
  2. 内存缓存(Memory Cache) :存储最近使用过的图片资源,读取速度较快。
  3. 磁盘缓存(Disk Cache) :存储已经下载过的图片资源,用于长期保存。
  4. 网络请求(Network Request) :当缓存中没有所需的图片时,才会发起网络请求。

三、内存缓存源码分析

3.1 核心类概述

Glide 的内存缓存主要由以下几个核心类实现:

  • LruResourceCache:继承自 Android 系统的 LruCache,是 Glide 默认的内存缓存实现,使用 LRU 算法管理缓存。
  • MemoryCache:是一个接口,定义了内存缓存的基本操作,如 putgetremove 等。
  • 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 方法:向缓存中添加资源,首先检查 keyresource 是否为 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 方法:根据 keyactiveEngineResources 中获取资源的弱引用。如果弱引用不为 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 详细分析
  • 构造函数:初始化 activeResourcesmemoryCachediskCacheServicesourceService 等属性,并将当前 Engine 实例设置为 memoryCache 的资源移除监听器。
  • load 方法:是资源加载的核心方法。首先调用 loadFromActiveResources 方法从活动资源缓存中查找资源,如果找到则通知监听器作业完成并返回 null。如果活动资源缓存中没有找到,则调用 loadFromCache 方法从内存缓存中查找资源。如果内存缓存中也没有找到,则创建 EngineJob 并开始加载资源。
  • loadFromActiveResources 方法:根据 keyactiveResources 中获取资源。如果找到资源,则增加资源的引用计数,并将其包装成 EngineResource 返回。
  • loadFromCache 方法:根据 keymemoryCache 中移除资源。如果找到资源,则将其包装成 EngineResource 返回。
  • onResourceRemoved 方法:当资源从内存缓存中移除时,调用 activeResourcesdeactivate 方法将其从活动资源缓存中释放。

四、磁盘缓存源码分析

4.1 核心类概述

Glide 的磁盘缓存主要由以下几个核心类实现:

  • DiskLruCacheWrapper:是 Glide 默认的磁盘缓存实现,基于 DiskLruCache 实现。
  • DiskCache:是一个接口,定义了磁盘缓存的基本操作,如 putgetdelete 等。
  • DiskCacheStrategy:是一个枚举类,定义了磁盘缓存的策略,如 ALLNONEDATARESOURCE 等。

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 的快照。SnapshotDiskLruCache 中的一个对象,代表缓存文件的一个快照,包含了文件的元数据和内容。
  • 返回输入流:如果快照存在,通过 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:不进行磁盘缓存,即 cacheSourcecacheResult 都为 false
    • DATA:只缓存原始数据,cacheSourcetruecacheResultfalse
    • RESOURCE:只缓存转换后的数据,cacheSourcefalsecacheResulttrue
    • ALL:缓存原始数据和转换后的数据,cacheSourcecacheResult 都为 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;
}
  • 缓存查找:首先调用 loadFromActiveResourcesloadFromCache 方法从活动资源缓存和内存缓存中查找资源。如果找到资源,通知监听器作业完成并返回 null
  • 磁盘缓存判断:如果内存缓存中没有找到资源,检查 DiskCacheStrategyisCacheSource 方法,判断是否应该从磁盘缓存加载资源。
  • 磁盘缓存加载:如果 isCacheSourcetrue,创建 EngineJob 并使用 DiskCacheGenerator 从磁盘缓存中加载资源。
  • 源数据加载:如果 isCacheSourcefalse,创建 EngineJob 并使用 SourceGenerator 直接从源数据(如网络)加载资源。

五、缓存键的生成与管理

5.1 缓存键的作用

缓存键是用于唯一标识一个缓存项的字符串。在 Glide 中,缓存键的生成非常重要,它直接影响到缓存的命中率和数据的一致性。通过合理的缓存键生成策略,可以确保相同的图片资源使用相同的缓存键,从而提高缓存的利用率。

5.2 缓存键的生成类

Glide 中缓存键的生成主要由 Key 接口及其实现类完成。常见的实现类有 ObjectKeyDataCacheKey 等。

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 +'}';}
}
  • 构造函数:接收 originalKeysignature 两个参数,分别存储原始键和签名键。
  • 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 手动清理

可以通过调用 MemoryCacheclearMemory 方法手动清空内存缓存,或者调用 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 手动清理

可以通过调用 DiskCacheclear 方法手动清空磁盘缓存。例如:

java

DiskCache diskCache = glide.getDiskCache();
diskCache.clear(); // 清空磁盘缓存

clear 方法会删除 DiskLruCache 中的所有缓存项,并重新打开磁盘缓存。

6.3 缓存清理的时机

6.3.1 内存不足时

Application 类的 onTrimMemory 方法中,可以根据内存级别调用 MemoryCachetrimMemory 方法进行内存缓存的清理。例如:

java

public class MyApplication extends Application {@Overridepublic void onTrimMemory(int level) {super.onTrimMemory(level);Glide.get(this).getMemoryCache().trimMemory(level);}
}

这样可以在系统内存不足时及时清理内存缓存,避免应用因内存不足而崩溃。

6.3.2 应用退出时

可以在应用退出时调用 MemoryCacheclearMemory 方法和 DiskCacheclear 方法清空所有缓存。例如:

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)错误;如果设置过小,会降低缓存的命中率,增加网络请求的次数。可以通过 GlideBuildersetMemoryCache 方法设置内存缓存的大小。例如:

java

GlideBuilder builder = new GlideBuilder(context);
builder.setMemoryCache(new LruResourceCache(20 * 1024 * 1024)); // 设置内存缓存大小为 20MB
Glide glide = builder.build(context);
  • 磁盘缓存:同样需要根据设备的存储空间和应用的需求合理设置磁盘缓存的大小。可以通过 GlideBuildersetDiskCache 方法设置磁盘缓存的大小。例如:

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 缓存清理的时机

缓存清理操作可能会消耗一定的系统资源,因此需要选择合适的时机进行清理,避免在用户使用过程中进行大规模的缓存清理,影响用户体验。可以考虑以下几种时机:

  • 应用处于后台时:当应用进入后台运行时,可以进行缓存清理操作。可以在 ActivityonStop 方法中添加缓存清理逻辑,但需要注意避免在短时间内频繁清理缓存。例如:

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;
}
  • 设备空闲时:可以使用 JobSchedulerWorkManager 来安排缓存清理任务,在设备空闲时执行。例如,使用 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 块对 putgetremove 方法进行同步处理,确保在多线程环境下对 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 的生命周期管理模块用于管理图片加载请求的生命周期,确保在 ActivityFragment 销毁时,取消未完成的图片加载请求,避免内存泄漏。缓存模块与生命周期管理模块的协作体现在资源的释放上。当 ActivityFragment 销毁时,会调用 Glide.with(this).clear() 方法,该方法会释放活动资源缓存中的资源,并进行相应的清理操作。例如:

java

@Override
protected void onDestroy() {super.onDestroy();Glide.with(this).clear();
}

这样可以确保在 ActivityFragment 销毁时,及时释放缓存资源,避免内存泄漏。

九、自定义缓存实现

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 来存储缓存项,并实现了 putgetremoveclearMemorytrimMemory 等方法。在 putremove 方法中,会调用 ResourceRemovedListeneronResourceRemoved 方法通知资源移除事件。

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 类来管理磁盘文件。实现了 getputdeleteclear 等方法,用于读取、写入、删除和清空磁盘缓存。

十、总结

Glide 的缓存模块是一个高度复杂且强大的系统,通过内存缓存和磁盘缓存的协同工作,显著提升了图片加载的效率和性能。内存缓存采用 LRU 算法,能够快速响应图片请求,减少用户等待时间;磁盘缓存则基于 DiskLruCache 实现,有效地持久化图片资源,降低网络流量消耗。

缓存键的精心设计确保了缓存的唯一性和一致性,使得相同的图片资源能够被准确地缓存和复用。同时,缓存的清理与管理机制保证了缓存的有效性和资源的合理利用,避免了缓存数据的过期和占用过多的系统资源。

在实际开发中,开发者需要根据应用的具体需求,合理设置缓存大小和选择合适的缓存策略。同时,要注意缓存过期、清理时机和多线程安全等问题,以确保应用的稳定性和性能。此外,Glide 提供了丰富的扩展接口,允许开发者自定义缓存实现,以满足特殊的业务需求。

随着 Android 技术的不断发展和应用场景的日益复杂,Glide 的缓存模块也将不断优化和完善,为开发者提供更加高效、灵活的图片缓存解决方案,助力打造更加优质的 Android 应用。

相关文章:

Android Glide 缓存模块源码深度解析

一、引言 在 Android 开发领域&#xff0c;图片加载是一个极为常见且关键的功能。Glide 作为一款被广泛使用的图片加载库&#xff0c;其缓存模块是提升图片加载效率和性能的核心组件。合理的缓存机制能够显著减少网络请求&#xff0c;降低流量消耗&#xff0c;同时加快图片显示…...

Mac安装Neo4j图数据库

通过Homebrew 安装&#xff08;推荐&#xff09; 打开mac终端&#xff1a; 1. 安装 Homebrew&#xff08;如果尚未安装&#xff09; /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"2. 安装 Neo4j brew insta…...

《A Gentle Introduction to Graph Neural Networks》-GNN的综述性论文

目录 一、什么数据可以表示成一张图 &#xff08;1&#xff09;什么是图&#xff1f; &#xff08;2&#xff09;如何表示图的属性 &#xff08;3&#xff09;images as graphs&#xff08;将图片表示为图&#xff09; &#xff08;4&#xff09;text as graphs&#xff08…...

[023-01-40].第40节:组件应用 - OpenFeign与 Sentinel 集成实现fallback服务降级

SpringCloud学习大纲 一、需求说明&#xff1a; 需求1&#xff1a;通过fallback属性进行统一配置 a.问题分析&#xff1a; 1.需要实现cloudalibaba-consumer-nacos-order83模块通过OpenFeign调用cloudalibaba-provider-payment9001 83服务通过OpenFeign调用 9001微服务&…...

设计模式-结构型模式-装饰器模式

概述 装饰器模式 : Decorator Pattern : 是一种结构型设计模式. 作用 &#xff1a; 允许你动态地给对象添加功能或职责&#xff0c;而无需修改其原始类的代码,非常的符合 开闭原则。 实现思路 &#xff1a;通过创建一个包装对象&#xff08;即装饰器&#xff09;&#xff0c;来…...

RK3588 编译 openssl

在编译 OpenSSL 时,你需要确保你的系统环境已经配置好了所有必要的依赖和编译工具。下面是一般步骤和一些常见问题的解决方案,特别是在使用 RK3588 这类的 ARM 处理器上。 1. 安装依赖 首先,你需要安装编译 OpenSSL 所需的依赖。这通常包括编译器(如 GCC)、make 工具、Per…...

Git前言(版本控制)

1.Git 目前世界上最先进的分布式版本控制系统。 git官网&#xff1a;https://git-scm.com/ 2.版本控制 2.1什么是版本控制 版本控制(Revision control)是一种在开发的过程中用于管理我们对文件、目录或工程等内容修改历史&#xff0c;方便查看更改历史记录备份以便恢复以前…...

visual studio配置opencv

文章目录 step1 下载opencvstep2 配置包含目录step 3 配置链接器step4 配置环境变量并重启vs2022step5 检查代码 step1 下载opencv 下载 opencv-4.8.0-windows.exe https://cloud.189.cn/web/share?codefUnqEb7naUra step2 配置包含目录 step 3 配置链接器 step4 配置环境变…...

docker修改daemon.json文件后无法启动

1.问题描述 使用阿里云docker镜像安装的docker&#xff0c;安装成功后默认可以启动。但是修改daemon.json配置后docker服务无法启动&#xff0c;提示如下错误&#xff1a; 从上图发现&#xff0c;docker服务默认使用阿里docker镜像仓库 2.解决方法 根据提示找到docker服务目…...

Linux网络:网络与操作系统1

本文是介绍网络的基本结构&#xff0c;以及和OS之间有什么关系 OSI七层模型 引入 使用网络是为了解决信息的长距离传送&#xff0c;那就需要解决四个问题&#xff1a; 接收方如何使用数据传输的可靠性主机如何定位数据包在局域网如何转发 人们选择用网络协议&#xff08;t…...

Manus(一种AI代理或自动化工具)与DeepSeek(一种强大的语言模型或AI能力)结合使用任务自动化和智能决策

一、Manus与DeepSeek差异 十分好奇DeepSeek和Manus究竟谁更厉害些&#xff0c;DeepSeek是知识型大脑&#xff0c;Manus则是全能型执行者。即DeepSeek专注于语言处理、知识整合与专业文本生成。其核心优势在于海量参数支持的深度学习和知识推理能力&#xff0c;例如撰写论文、润…...

Python个人学习笔记(14):函数(匿名函数、内置函数(下)、三元表达式)

九、匿名函数 lambda表达式 语法规则&#xff1a; 变量 lambda 参数1,参数2,…:返回值 例&#xff1a;用lambda简化下述操作 def func(a,b):return ab ret func(1, 2) print(ret)代码&#xff1a; fn lambda a,b:ab print(fn) print(fn(12,13))结果&#xff1a; <fun…...

姚安娜新剧瘦了一圈,《仁心俱乐部》急诊医生顾诗宜在线上岗

《仁心俱乐部》在芒果 TV 播出&#xff0c;湖南卫视金鹰独播剧场也随之播出&#xff0c;这一剧集受到了不少观众的关注。姚安娜在剧中饰演的急诊科医生顾诗宜&#xff0c;她为患者检查身体时动作娴熟&#xff0c;与患者沟通时展现出的耐心和专注&#xff0c;都展现出很高的专业…...

【PyCharm】Python和PyCharm的相互关系和使用联动介绍

李升伟 整理 Python 是一种广泛使用的编程语言&#xff0c;而 PyCharm 是 JetBrains 开发的专门用于 Python 开发的集成开发环境&#xff08;IDE&#xff09;。以下是它们的相互关系和使用联动的介绍&#xff1a; 1. Python 和 PyCharm 的关系 Python&#xff1a;一种解释型、…...

【ES6】基础特性总结

概述 仅个人使用&#xff0c;复习ES6的笔记&#xff0c;比较粗糙&#xff0c;仅适用于浏览器端。 数据类型 ES6&#xff08;ECMAScript 2015&#xff09;引入了一些新的数据类型和对现有数据类型的扩展。以下是ES6中数据类型的一个简要总结表格&#xff1a; 数据类型描述Nu…...

串口数据记录仪DIY,体积小,全开源

作用 产品到客户现场出现异常情况&#xff0c;这个时候就需要一个日志记录仪、黑匣子&#xff0c;可以记录产品的工作情况&#xff0c;当出现异常时&#xff0c;可以搜集到上下文的数据&#xff0c;从而判断问题原因。 之前从网上买过&#xff0c;但是出现过丢数据的情况耽误…...

无障碍阅读(Web Accessibility)NVDA打开朗读查看器后,enter键不生效的原因

用NVDA测试Web Accessibility时&#xff0c;打开朗读查看器&#xff0c;enter键会无效&#xff0c;而不打开测试器&#xff0c;就没有问题&#xff0c;很大原因是被应用的元素不是可聚焦的&#xff0c;解决方法尝试&#xff1a; 将标签改为可聚焦的语义化标签&#xff0c;如 b…...

基于docker+python+paddleocr构建自己本地化ocr服务

1、使用FastAPI创建服务实例 1.1、正常程序 from fastapi import FastAPI, UploadFile, File, HTTPException from typing import List from paddleocr import PaddleOCR import numpy as np from PIL import Image import io import loggingapp FastAPI(title"游戏截图…...

【视频】V4L2、ffmpeg、OpenCV中对YUV的定义

1、常见的YUV格式 1.1 YUV420 每像素16位 IMC1:YYYYYYYY VV-- UU– IMC3:YYYYYYYY UU-- VV– 每像素12位 I420: YYYYYYYY UU VV =>YUV420P YV12: YYYYYYYY VV UU =>YUV420P NV12: YYYYYYYY UV UV =>YUV420SP(最受欢迎格式) NV21: YYYYYYYY VU VU =>YUV420SP…...

歌词相关实现

歌词相关 歌词数据模型&#xff1a; // Lyric.swift class Lyric: BaseModel {/// 是否是精确到字的歌词var isAccurate:Bool false/// 所有的歌词var datum:Array<LyricLine>! }// LyricLine.swift class LyricLine: BaseModel {/// 整行歌词var data:String!/// 开始…...

51单片机Proteus仿真速成教程——P1-软件与配置+Proteus绘制51单片机最小系统+新建程序模版

前言&#xff1a;本文主要围绕 51 单片机最小系统的绘制及程序模板创建展开。首先介绍了使用 Proteus 绘制 51 单片机最小系统的详细步骤&#xff0c;包括软件安装获取途径、工程创建、器件添加&#xff08;如单片机 AT89C51、晶振、电容、电阻、按键等&#xff09;、外围电路&…...

使用 pytesseract 进行 OCR 识别:以固定区域经纬度提取为例

引言 在智能交通、地图定位等应用场景中&#xff0c;经常会遇到需要从图像中提取经纬度信息的需求。本篇文章将介绍如何利用 Python 的 pytesseract 库结合 PIL 对图像进行预处理&#xff0c;通过固定区域裁剪&#xff0c;来有效地识别出图像上显示的经纬度信息。 1. OCR 与 …...

【18】单片机编程核心技巧:变量赋值与高位填充机制

【18】单片机编程核心技巧&#xff1a;变量赋值与高位填充机制 七律 变量赋值探秘 单字赋多字疑云开&#xff0c;高位零填自天来。 清零保守虽稳妥&#xff0c;强制转换更悠哉。 实验验证真章显&#xff0c;编译器间无异态。 嵌入式海行舟稳&#xff0c;类型分明避坑台。 注释…...

网络安全系统集成

随着信息技术的迅猛发展&#xff0c;网络安全问题变得越来越突出。为了应对这一挑战&#xff0c;软考网络安全系统集成应运而生&#xff0c;成为众多企业和机构的重要需求。软考网络安全系统集成旨在培养具备网络安全系统设计、实施和维护能力的专业人才&#xff0c;以满足国家…...

【51单片机】程序实验15.DS18B20温度传感器

主要参考学习资料&#xff1a;B站【普中官方】51单片机手把手教学视频 开发资料下载链接&#xff1a;http://www.prechin.cn/gongsixinwen/208.html 单片机套装&#xff1a;普中STC51单片机开发板A4标准版套餐7 目录 DS18B20介绍主要特性内部结构控制时序初始化时序写时序读时序…...

Vue项目上传到GitHub,vscode拉取vue项目更新后推送到GitHub上

1、新建Vue项目 2、在GitHub新建仓库 3、留意建立好仓库后提示的命令 4、进入vue项目目录&#xff0c;在空白处点击鼠标右键选择git bash here 5、输入命令 git init git add . git commit -m "注释内容" 输入之前创建GitHub仓库后记下的代码的第一句 git remote…...

数字孪生技术在工业制造中的应用探索

一、数字孪生&#xff1a;工业4.0的虚实纽带 1.1 技术定义与发展脉络 数字孪生&#xff08;Digital Twin&#xff09;通过实时数据映射&#xff0c;在虚拟空间构建物理实体的动态镜像。其演进历程&#xff1a; 概念萌芽&#xff08;2002年&#xff09;&#xff1a;NASA首次提…...

# linux有哪些桌面环境?有哪些显示服务器协议及显示服务器?有哪些用于开发图形用户界面的工具包?

linux有哪些桌面环境&#xff1f;有哪些显示服务器协议及显示服务器&#xff1f;有哪些用于开发图形用户界面的工具包&#xff1f; 文章目录 linux有哪些桌面环境&#xff1f;有哪些显示服务器协议及显示服务器&#xff1f;有哪些用于开发图形用户界面的工具包&#xff1f;1 显…...

【心理课堂】学习软件的道路上若感到了困难和迷茫怎么办

在科技飞速发展的今天&#xff0c;软件领域以其广阔的发展前景和丰厚的薪资待遇吸引着众多人投身其中。然而&#xff0c;学习软件并非一帆风顺&#xff0c;在这个过程中&#xff0c;我们难免会遇到困难和迷茫。那么&#xff0c;当我们在学习软件的道路上感到力不从心时&#xf…...

【Docker项目实战】使用Docker与Caddy部署BanBan任务管理工具

【Docker项目实战】使用Docker部署BanBan任务管理工具 一、BanBan介绍1.1 BanBan简介1.2 主要特点1.3 使用场景二、本次实践规划2.1 本地环境规划2.2 本次实践介绍三、本地环境检查3.1 检查Docker服务状态3.2 检查Docker版本3.3 检查docker compose 版本四、下载BanBan镜像五、…...