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:是一个接口,定义了内存缓存的基本操作,如 put、get、remove 等。
  • ActiveResources:用于管理活动资源缓存,存储正在使用的图片资源。

3.2 LruResourceCache 类分析

3.2.1 类定义及属性

java

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

    @Override
    public void setResourceRemovedListener(ResourceRemovedListener listener) {
        // 设置资源移除监听器
        this.listener = listener;
    }

    @Override
    protected int sizeOf(Key key, Resource<?> resource) {
        // 计算资源的大小,用于 LRU 算法的缓存管理
        return resource.getSize();
    }

    @Override
    protected void entryRemoved(boolean evicted, Key key, Resource<?> oldValue, Resource<?> newValue) {
        // 当缓存项被移除时,调用资源移除监听器的回调方法
        if (listener != null && oldValue != null) {
            listener.onResourceRemoved(oldValue);
        }
    }

    @Override
    public Resource<?> put(Key key, Resource<?> resource) {
        // 向缓存中添加资源
        if (key == null || resource == null) {
            return null;
        }
        return super.put(key, resource);
    }

    @Override
    public Resource<?> remove(Key key) {
        // 从缓存中移除资源
        if (key == null) {
            return null;
        }
        return super.remove(key);
    }

    @Override
    public void clearMemory() {
        // 清空缓存
        evictAll();
    }

    @Override
    public 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

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

    // 内部类,用于存储资源的弱引用
    @Synthetic
    static final class ResourceWeakReference extends WeakReference<Resource<?>> {
        // 资源的 Key
        @NonNull
        final 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
     */
    @Nullable
    public 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

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 引擎作业实例
     */
    @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;
        }

        // 如果缓存中没有找到资源,创建引擎作业并开始加载
        EngineJob<R> engineJob =
                EngineJobFactory.build(
                        key,
                        isSkipMemoryCache,
                        false, // useUnlimitedSourceExecutorPool
                        false, // useAnimationPool
                        onlyRetrieveFromCache,
                        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) {
            // 如果找到资源,将其包装成 EngineResource
            if (cached instanceof EngineResource) {
                return (EngineResource<?>) cached;
            } else {
                return new EngineResource<>(cached, true, false);
            }
        }
        return null;
    }

    @Override
    public 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

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 实例
     */
    @NonNull
    public static DiskCache get(@NonNull File directory, long maxSize) {
        return new DiskLruCacheWrapper(directory, maxSize);
    }

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

    @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) {
                    // 忽略异常
                }
            }
        }
    }

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

    @Override
    public void clear() {
        synchronized (diskLruCacheLock) {
            // 确保 DiskLruCache 已经初始化
            initializeIfNeeded();
            if (diskLruCache != null) {
                try {
                    // 删除 DiskLruCache 中的所有缓存项
                    diskLruCache.delete();
                    // 重新打开 DiskLruCache
                    diskLruCache = 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 {
                // 打开 DiskLruCache
                diskLruCache = 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

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

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

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

java 复制代码
@Override
public void clear() {
    synchronized (diskLruCacheLock) {
        // 确保 DiskLruCache 已经初始化
        initializeIfNeeded();
        if (diskLruCache != null) {
            try {
                // 删除 DiskLruCache 中的所有缓存项
                diskLruCache.delete();
                // 重新打开 DiskLruCache
                diskLruCache = 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

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

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, // useUnlimitedSourceExecutorPool
                        false, // useAnimationPool
                        onlyRetrieveFromCache,
                        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, // useUnlimitedSourceExecutorPool
                    false, // useAnimationPool
                    onlyRetrieveFromCache,
                    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

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

    @Override
    public boolean equals(Object o) {
        if (o instanceof ObjectKey) {
            ObjectKey other = (ObjectKey) o;
            // 判断两个 ObjectKey 是否相等,通过比较原始对象是否相等
            return object.equals(other.object);
        }
        return false;
    }

    @Override
    public int hashCode() {
        // 返回原始对象的哈希码
        return object.hashCode();
    }

    @Override
    public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
        // 将原始对象的字符串表示转换为字节数组,并更新 MessageDigest 对象
        messageDigest.update(object.toString().getBytes(CHARSET));
    }

    @NonNull
    @Override
    public String toString() {
        return "ObjectKey{" + "object=" + object + '}';
    }
}
  • 构造函数 :接收一个 object 参数,将其存储在 object 属性中。
  • equals 方法 :判断两个 ObjectKey 是否相等,通过比较原始对象是否相等来实现。
  • hashCode 方法:返回原始对象的哈希码,用于在哈希表中查找。
  • updateDiskCacheKey 方法 :将原始对象的字符串表示转换为字节数组,并更新 MessageDigest 对象,用于生成磁盘缓存键。
  • toString 方法 :返回 ObjectKey 的字符串表示,方便调试。
5.2.2 DataCacheKey 类分析

java

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

    @Override
    public boolean equals(Object o) {
        if (o instanceof DataCacheKey) {
            DataCacheKey other = (DataCacheKey) o;
            // 判断两个 DataCacheKey 是否相等,需要同时比较原始键和签名键是否相等
            return originalKey.equals(other.originalKey) && signature.equals(other.signature);
        }
        return false;
    }

    @Override
    public int hashCode() {
        // 根据原始键和签名键的哈希码计算 DataCacheKey 的哈希码
        return originalKey.hashCode() * 31 + signature.hashCode();
    }

    @Override
    public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
        // 分别调用原始键和签名键的 updateDiskCacheKey 方法更新 MessageDigest 对象
        originalKey.updateDiskCacheKey(messageDigest);
        signature.updateDiskCacheKey(messageDigest);
    }

    @NonNull
    @Override
    public 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

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

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

java 复制代码
DiskCache diskCache = glide.getDiskCache();
diskCache.clear(); // 清空磁盘缓存

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

6.3 缓存清理的时机

6.3.1 内存不足时

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

java

java 复制代码
public class MyApplication extends Application {
    @Override
    public void onTrimMemory(int level) {
        super.onTrimMemory(level);
        Glide.get(this).getMemoryCache().trimMemory(level);
    }
}

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

6.3.2 应用退出时

可以在应用退出时调用 MemoryCache 的 clearMemory 方法和 DiskCache 的 clear 方法清空所有缓存。例如:

java

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

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

java

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

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

java 复制代码
Key signature = new ObjectKey("new_image_version_1");
Glide.with(context)
     .load(imageUrl)
     .signature(signature)
     .into(imageView);
  • 定期清理缓存:可以在应用中设置一个定时任务,定期清理磁盘缓存。例如,每天凌晨执行一次缓存清理操作:

java

java 复制代码
// 假设在 Application 类中设置定时任务
public class MyApplication extends Application {
    @Override
    public 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
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public 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

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

java 复制代码
// 定义一个缓存清理工作
public class CacheCleanupWorker extends Worker {
    public CacheCleanupWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }

    @NonNull
    @Override
    public 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

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

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

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

java 复制代码
@Override
protected void onDestroy() {
    super.onDestroy();
    Glide.with(this).clear();
}

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

九、自定义缓存实现

9.1 自定义内存缓存

如果默认的 LruResourceCache 不能满足需求,可以自定义内存缓存。需要实现 MemoryCache 接口,并根据自己的需求实现相应的方法。例如:

java

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;

    @Override
    public void setResourceRemovedListener(ResourceRemovedListener listener) {
        this.listener = listener;
    }

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

    @Override
    public Resource<?> get(Key key) {
        if (key == null) {
            return null;
        }
        return cache.get(key);
    }

    @Override
    public Resource<?> remove(Key key) {
        if (key == null) {
            return null;
        }
        Resource<?> removed = cache.remove(key);
        if (removed != null && listener != null) {
            listener.onResourceRemoved(removed);
        }
        return removed;
    }

    @Override
    public void clearMemory() {
        for (Map.Entry<Key, Resource<?>> entry : cache.entrySet()) {
            Resource<?> resource = entry.getValue();
            if (resource != null && listener != null) {
                listener.onResourceRemoved(resource);
            }
        }
        cache.clear();
    }

    @Override
    public 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

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

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

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

    @Override
    public void delete(Key key) {
        File file = getFileForKey(key);
        if (file.exists()) {
            file.delete();
        }
    }

    @Override
    public 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 应用。

相关推荐
千里马学框架3 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台3 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone3 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc3 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo3 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077003 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
全栈弄潮儿²⁰²⁴3 天前
AI Agent 开发实战(30):限流、缓存与成本控制
人工智能·gpt·缓存·agent·限流·agi·成本控制
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen3 天前
属性动画原理与高级动画实现
android·kotlin·canvas