Glide缓存源码解析

时间:2022-06-24 09:19:15

上一篇讲了Glide加载图片的整个流程的源码的解析,写了很长,因为Glide的源码比较复杂,没看过的朋友,可以去看一下:http://blog.csdn.net/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简单一点,希望能对两个框架做一个深层次的比较。。。。