Glide缓存源码解析

上一篇讲了Glide加载图片的整个流程的源码的解析,写了很长,因为Glide的源码比较复杂,没看过的朋友,可以去看一下:http://blog.youkuaiyun.com/jieqiang3/article/details/76599815。因为上一篇文章篇幅太长的缘故,所以,缓存这一块就打算另起一篇了说了。。ok,废话就不多少了,进入正题。

先大致讲一下Glide的缓存流程吧,其实在这方面目前流行的一些图片加载的框架还是比较统一的,Glide的缓存机制分为两层,第一层是内存缓存,第二层就是硬盘缓存。首页,会在内存中先缓存,然后将资源缓存到内存里。至于加载的时候呢,一开始先去检查内存这一层级有没有缓存,有的话则直接加载,没有的话则到硬盘缓存这一层里去检查是否有缓存,有的话直接加载,没有的话,就只能去网络加载了。

ok,先来讲内存缓存,内存缓存Glide是默认自动开启了的,当然你也可能遇到特殊情况会需要把Glide内存缓存这个功能去掉,Glide也提供了方法来支持开发者去掉;

Glide.with(context)
     .load(url)
     .skipMemoryCache(true)    //禁止内存缓存的功能
     .into(imageView);

在上一篇Glide加载图片的流程中我们知道,Glide.get(context)的时候创建了一个GlideBuilder对象,这个对象创建了一个glide对象,我们来关注这个创建的过程,也就是createGlide()方法:

Glide createGlide() {
        if (sourceService == null) {
            final int cores = Math.max(1, Runtime.getRuntime().availableProcessors());
            sourceService = new FifoPriorityThreadPoolExecutor(cores);
        }
        if (diskCacheService == null) {
            diskCacheService = new FifoPriorityThreadPoolExecutor(1);
        }

        MemorySizeCalculator calculator = new MemorySizeCalculator(context);
        if (bitmapPool == null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
                int size = calculator.getBitmapPoolSize();
                bitmapPool = new LruBitmapPool(size);
            } else {
                bitmapPool = new BitmapPoolAdapter();
            }
        }

        if (memoryCache == null) {
            memoryCache = new LruResourceCache(calculator.getMemoryCacheSize());
        }

        if (diskCacheFactory == null) {
            diskCacheFactory = new InternalCacheDiskCacheFactory(context);
        }

        if (engine == null) {
            engine = new Engine(memoryCache, diskCacheFactory, diskCacheService, sourceService);
        }

        if (decodeFormat == null) {
            decodeFormat = DecodeFormat.DEFAULT;
        }

        return new Glide(engine, memoryCache, bitmapPool, context, decodeFormat);
    }

这段代码充分说明了文章开始我说的Glide的二级缓存原理。这里创建了一个bitmapPool的对象,这个BitmapPool其实是为了复用bitmap的对象,也就是防止频繁的去创建bitmap的作用。我们先看一下bitmapPool的创建流程,因为getBitmapPoolSize()方法就是return了bitmapPoolSize这个变量,所以我就定位到MemorySizeCalculator类的构造方法:

// Visible for testing.
    MemorySizeCalculator(Context context, ActivityManager activityManager, ScreenDimensions screenDimensions) {
        this.context = context;
        final int maxSize = getMaxSize(activityManager);

        final int screenSize = screenDimensions.getWidthPixels() * screenDimensions.getHeightPixels()
                * BYTES_PER_ARGB_8888_PIXEL;

        int targetPoolSize = screenSize * BITMAP_POOL_TARGET_SCREENS;
        int targetMemoryCacheSize = screenSize * MEMORY_CACHE_TARGET_SCREENS;

        if (targetMemoryCacheSize + targetPoolSize <= maxSize) {
            memoryCacheSize = targetMemoryCacheSize;
            bitmapPoolSize = targetPoolSize;
        } else {
            int part = Math.round((float) maxSize / (BITMAP_POOL_TARGET_SCREENS + MEMORY_CACHE_TARGET_SCREENS));
            memoryCacheSize = part * MEMORY_CACHE_TARGET_SCREENS;
            bitmapPoolSize = part * BITMAP_POOL_TARGET_SCREENS;
        }

        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "Calculated memory cache size: " + toMb(memoryCacheSize) + " pool size: " + toMb(bitmapPoolSize)
                    + " memory class limited? " + (targetMemoryCacheSize + targetPoolSize > maxSize) + " max size: "
                    + toMb(maxSize) + " memoryClass: " + activityManager.getMemoryClass() + " isLowMemoryDevice: "
                    + isLowMemoryDevice(activityManager));
        }
    }

我们发现BitmapPool的大小是根据当前设备的屏幕大小和可用内存计算得到的。当然,如果开发者想要自定义配置BitmapPool的大小也很简单,MemoryCategory提供了三个常量可供开发者选择,HIGH\NORMAL\LOW,分别是初识缓存大小的1.5、1.0、0.5倍。在一些有大量图片加载的页面上的时候建议加大BitmapPool的缓存大小,这样可以加快图片的缓存速度,从而提升性能。
扯远了,回到createGlide()方法,我们发现创建了一个LruResourceCache对象memoryCache,这也就是我们内存缓存的东西了。我们来看LruResourece类:

public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache {
    private ResourceRemovedListener listener;

    /**
     * Constructor for LruResourceCache.
     *
     * @param size The maximum size in bytes the in memory cache can use.
     */
    public LruResourceCache(int size) {
        super(size);
    }

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

    @Override
    protected void onItemEvicted(Key key, Resource<?> item) {
        if (listener != null) {
            listener.onResourceRemoved(item);
        }
    }

    @Override
    protected int getSize(Resource<?> item) {
        return item.getSize();
    }

    @SuppressLint("InlinedApi")
    @Override
    public void trimMemory(int level) {
        if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
            // Nearing middle of list of cached background apps
            // Evict our entire bitmap cache
            clearMemory();
        } else if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
            // Entering list of cached background apps
            // Evict oldest half of our bitmap cache
            trimToSize(getCurrentSize() / 2);
        }
    }
}

我们发现这个类继承了LruCache类,也就是说Glide内存缓存是通过LruCache算法实现的,也就是近期最少使用算法。其实就是把强引用对象保存到LinkedHashMap里,然后在内存空间快要用光的时候,把最近最少使用的对象从内存中去掉。LruCache这个类我相信一个学Android的入门学图片缓存的时候都接触过,网上也有很多很详细的解释,这里就不详述了。。
回到createGlide()方法,创建了LruResourceCache对象memoryCache之后,传入了Engine类中,并且创建了一个他的对象,Engine这个类我们应该很熟悉,在上一篇谈论加载流程的文章中我们探讨过这个类,ok,那就再来以缓存的角度再来看一下这个类。这里重点来看load方法:

public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
            DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
            Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
        Util.assertMainThread();
        long startTime = LogTime.getLogTime();

        final String id = fetcher.getId();
        EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
                loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
                transcoder, loadProvider.getSourceEncoder());

        EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
        if (cached != null) {
            cb.onResourceReady(cached);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Loaded resource from cache", startTime, key);
            }
            return null;
        }

        EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
        if (active != null) {
            cb.onResourceReady(active);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Loaded resource from active resources", startTime, key);
            }
            return null;
        }

        EngineJob current = jobs.get(key);
        if (current != null) {
            current.addCallback(cb);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Added to existing load", startTime, key);
            }
            return new LoadStatus(cb, current);
        }

        EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
        DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
                transcoder, diskCacheProvider, diskCacheStrategy, priority);
        EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
        jobs.put(key, engineJob);
        engineJob.addCallback(cb);
        engineJob.start(runnable);

        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Started new load", startTime, key);
        }
        return new LoadStatus(cb, engineJob);
    }

首先,我们发现了Glide生成key的规律,现调用了fetcher.getId()方法获得一个id,然后将这个id和signature, width, height,loadProvide.getCacheDecoder()等都传进去,然后生成了一个key值,所以Glide缓存不会出现key重复的现象。接下去往下看,我们看到了两个方法,一个是loadFromCache,一个是loadFromActiviveResources,先调用前者,如果获取到就直接调用cb.onResourceReady(cached)回调成功,否则则调用后者成功则一样,如果两者都不成功,接下去的就是开启线程去加载图片的逻辑了。。。
ok,先来看loadFromCache方法:

 private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
        if (!isMemoryCacheable) {
            return null;
        }

        EngineResource<?> cached = getEngineResourceFromCache(key);
        if (cached != null) {
            cached.acquire();
            activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
        }
        return cached;
    }

    @SuppressWarnings("unchecked")
    private EngineResource<?> getEngineResourceFromCache(Key key) {
        Resource<?> cached = cache.remove(key);

        final EngineResource result;
        if (cached == null) {
            result = null;
        } else if (cached instanceof EngineResource) {
            // Save an object allocation if we've cached an EngineResource (the typical case).
            result = (EngineResource) cached;
        } else {
            result = new EngineResource(cached, true /*isCacheable*/);
        }
        return result;
    }

这里调用了getEngineResourceFromCache方法来获取缓存,可以看到,首先调用了cache.remove(key),这个cache是什么呢,往上看其实就是之前从LruResourceCache 中获取到的缓存图片,然后加入到activeResources中,put的过程中传入了一个新建的ResourceWeakReference,这里用到了弱引用,其实只是为了保护这些图片防治被Lrucache算法回收。
再来看loadFromActiveResources方法:

private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
        if (!isMemoryCacheable) {
            return null;
        }

        EngineResource<?> active = null;
        WeakReference<EngineResource<?>> activeRef = activeResources.get(key);
        if (activeRef != null) {
            active = activeRef.get();
            if (active != null) {
                active.acquire();
            } else {
                activeResources.remove(key);
            }
        }

        return active;
    }

其实就是从刚才到activeResources中取出刚才存进去的图片资源。
ok,内存缓存的逻辑基本上就是这些了。。。

再来讲讲硬盘缓存,上篇文章中我们在提Glide用法的时候有提到4个缓存参数,也就是基本的Glide对于硬盘缓存的配置。
在上一篇Glide加载流程中我们已经知道,我们是在DecodeJob类下的loadData()获取图片,然后通过decodeSource()方法解码获取图片资源的。ok,我们再贴一下这个方法的代码:

private Resource<T> decodeFromSourceData(A data) throws IOException {
    final Resource<T> decoded;
    if (diskCacheStrategy.cacheSource()) {
        decoded = cacheAndDecodeSourceData(data);
    } else {
        long startTime = LogTime.getLogTime();
        decoded = loadProvider.getSourceDecoder().decode(data, width, height);
    }
    return decoded;
}

private Resource<T> cacheAndDecodeSourceData(A data) throws IOException {
    long startTime = LogTime.getLogTime();
    SourceWriter<A> writer = new SourceWriter<A>(loadProvider.getSourceEncoder(), data);
    diskCacheProvider.getDiskCache().put(resultKey.getOriginalKey(), writer);
    startTime = LogTime.getLogTime();
    Resource<T> result = loadFromCache(resultKey.getOriginalKey());
    return result;
}

我们会发现,这个方法中,先去判断了是否允许缓存图片,也就是之前的配置,如果允许,就去调用cacheAndDecodeSourceData()方法,然后我们再来看这个方法,这个方法一目了然,首先调用了getDiskCache()方法获取实例对象,然后调用put()方法写入缓存。这里我们发现,key的值跟之前内存缓存那里有些不同,让我们看看getOriginalKey()方法

public Key getOriginalKey() {
        if (originalKey == null) {
            originalKey = new OriginalKey(id, signature);
        }
        return originalKey;
    }

其实也好理解,硬盘缓存缓存的本来就是原始图片,所以不需要内存缓存那里这么多图片参数,所以也就很简单了。回到之前cacheAndDecodeSourceData方法,了解了key之后,我们来看loadFromCache()方法;

private Resource<T> loadFromCache(Key key) throws IOException {
        File cacheFile = diskCacheProvider.getDiskCache().get(key);
        if (cacheFile == null) {
            return null;
        }

        Resource<T> result = null;
        try {
            result = loadProvider.getCacheDecoder().decode(cacheFile, width, height);
        } finally {
            if (result == null) {
                diskCacheProvider.getDiskCache().delete(key);
            }
        }
        return result;
    }

这段代码其实就是一个通过key得到硬盘缓存的图片,如果空返回null,如果不空则做相应解码操作以后并返回这么一个逻辑过程,差不多就是这样。

相对Glide加载流程来说,缓存这篇篇幅就短了很多,因为很多主要流程其实在上一篇都已经提到了。Glide整体来说封装还是很出色的,源码结构还是比较复杂的,外放了很多接口方法供开发者使用,接下去会去探究下Fresco的源码,据说源码角度来说会比Glide简单一点,希望能对两个框架做一个深层次的比较。。。。

### Glide 源码解析 Glide 是一种功能强大的 Android 图片加载库,其设计目标是简化开发者在应用程序中加载、缓存和显示图像的过程。为了深入理解 Glide源码结构及其工作原理,可以从以下几个方面展开: #### 1. **核心类与模块** Glide 的架构由多个核心组件组成,主要包括 `RequestManager`、`Engine` 和 `ModelLoader` 等。 - **RequestManager**: 负责管理请求生命周期并提供接口供外部调用[^2]。通过 `with()` 方法创建实例,并绑定到特定上下文中。 - **Engine**: 执行实际的图片加载任务,负责协调缓存策略以及解码操作[^5]。它内部实现了三级缓存机制(内存缓存 -> 数据缓存 -> 网络加载)。 - **ModelLoader**: 定义如何将模型对象转换为目标数据流(如文件路径转为输入流)。例如,在加载 GIF 动态图时会使用专门的 ModelLoader 实现[^3]。 #### 2. **加载流程剖析** 当调用如下代码片段时: ```java Glide.with(context) .load(url) .into(imageView); ``` 以下是具体执行过程概述: ##### (1)构建 Request 对象 通过 `SingleRequest.obtain()` 创建单次请求实例,并设置必要参数(如 URL 地址、ImageView 组件等)[^5]。 ##### (2)触发 Engine 加载逻辑 进入 `engine.load()` 方法后,首先检查是否存在匹配键值对的快速命中情况;如果未发现,则进一步尝试从磁盘或其他存储位置检索资源副本[^5]。 ##### (3)启动 Decode Job 一旦确认需发起新作业或者等待已有任务完成,便会调用 `waitForExistingOrStartNewJob()` 函数开启异步线程池调度模式下的 decode 工作。 ##### (4)逐级查找合适 Generator 按照预定义优先级顺序依次测试三种可能的数据生产者——即 ResourceGenerator、DataCacheGenerator 及 SourceGenerator 是否满足当前需求。对于后者而言,还需借助对应的 ModelLoader 来获取远程服务器上的原始素材内容。 #### 3. **缓存机制详解** 正如前面提到过的那样,Glide 支持三层次级别的高速缓冲方案以提升性能表现: - **Memory Cache (Active Resources)**: 使用 HashMap 存储经过加工后的最终呈现形式(Bitmap 或 Drawable 类型),便于即时复用而无需重复计算开销; - **Disk Cache**: 利用 LRU 算法管理有限空间内的持久化记录集合,从而减少频繁网络交互带来的延迟影响; - **Source Data Cache**: 针对尚未被完全转化的目标材料实施暂存措施,以便后续阶段可直接读取而非重新拉取全部资料[^5]。 --- ### 示例代码展示 以下是一个简单的自定义配置案例演示如何调整默认行为: ```java // 设置全局选项 new GlideBuilder(applicationContext) .setLogLevel(Log.ERROR) // 修改日志级别 .setDefaultRequestOptions( new RequestOptions() .placeholder(R.drawable.placeholder_image) .error(R.drawable.error_image)) .build(); // 局部覆盖部分属性 GlideApp.with(context) .asGif() // 明确指明期望返回格式为动画类型 .apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)) // 强制启用全面缓存策略 .load(gifUrl) .listener(new RequestListener<GifDrawable>() { @Override public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<GifDrawable> target, boolean isFirstResource) { Log.e("Glide", "Error loading gif!", e); return false; } @Override public boolean onResourceReady(GifDrawable resource, Object model, Target<GifDrawable> target, DataSource dataSource, boolean isFirstResource) { Log.d("Glide", "Successfully loaded gif!"); return false; } }) .into(imageView); ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值