Glide在缓存功能上也是分成了两个模块,一个内存缓存,一个硬盘缓存。内存缓存的主要作用是防止应用重复将图片数据读取到内存当中,硬盘缓存的主要作用是防止应用重复从网络或其他地方重复下载和读取数据。
缓存的图片资源
分为两类:原始图片和转换后的图片(经过尺寸缩放和大小压缩等处理后的图片)
内存缓存只缓存转换过后的图片,而硬盘缓存可缓存原始图片和转换过后的图片,用户自行设置
缓存key
决定缓存key的参数有十个之多,来看一下生成缓存key的代码。
Engine # load()
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
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();
// 这个字符串就是我们要加载的图片的唯一标识,如果是网络图片,id就是这个图片的url
final String id = fetcher.getId();
// 生成缓存key
EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
transcoder, loadProvider.getSourceEncoder());
...
}
...
}
EngineKey的源码主要是重写了equals()和hashCode()方法,保证只有传入EngineKey的所有参数都相同的情况下才认为是同一个EngineKey对象。
内存缓存
默认情况下,Glide自动就是开启内存缓存的。Glide内存缓存的实现也是使用的LruCache算法,它还结合了一种弱引用的机制,共同完成内存缓存。
LruCache也叫近期最少使用算法,主要原理就是把最近使用的对象用强引用存储在LinkedHashMap中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除
创建缓存对象LruResourceCache
LruResourceCache对象是在创建 Glide 对象时创建的,那么Glide对象在哪创建?
在执行流程的第二步load()方法中,当时loadGeneric()方法中会调用Glide.buildStreamModelLoader()来获取一个ModelLoader对象。
Glide # buildStreamModelLoader()
public static <T, Y> ModelLoader<T, Y> buildModelLoader(Class<T> modelClass, Class<Y> resourceClass,
Context context) {
if (modelClass == null) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Unable to load null model, setting placeholder only");
}
return null;
}
return Glide.get(context).getLoaderFactory().buildModelLoader(modelClass, resourceClass);
}
// 实现的是一个单例功能
public static Glide get(Context context) {
if (glide == null) {
synchronized (Glide.class) {
if (glide == null) {
Context applicationContext = context.getApplicationContext();
List<GlideModule> modules = new ManifestParser(applicationContext).parse();
GlideBuilder builder = new GlideBuilder(applicationContext);
for (GlideModule module : modules) {
module.applyOptions(applicationContext, builder);
}
// 创建Glide对象
glide = builder.createGlide();
for (GlideModule module : modules) {
module.registerComponents(applicationContext, glide);
}
}
}
}
return glide;
}
GlideBuilder # 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) {
// 创建LruResourceCache对象,它就是Glide实现内存缓存所使用的LruCache对象
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);
}
这里只是把准备工作做好了,接下来看一下内存缓存是如何实现的,再回到Engine的load方法
内存缓存读取
Engine # load()
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
...
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的图片加载过程中会调用两个方法来获取内存缓存。
Engine # loadFromCache() & loadFromActiveResources()
使用activeResources来缓存正在使用中的图片,可以保护这些图片不会被LruCache算法回收掉。
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
private final MemoryCache cache;
private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
...
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
// 如果我们代码里调用了skipMemoryCache(true),那这里的isMemoryCacheable就会是false
// isMemoryCacheable表示内存缓存是否被禁用
if (!isMemoryCacheable) {
return null;
}
// 使用缓存key从cache里面取图片
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
// 将这个缓存图片存储到activeResources中
// activeResources是一个弱引用的HashMap,用来缓存正在使用中的图片
activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
}
// 内存中的图片
return cached;
}
private EngineResource<?> getEngineResourceFromCache(Key key) {
// 这里的cache就是构建glide对象时创建的LruResourceCache
// cache.remove()表示当我们从LruResourceCache中获取缓存图片之后会将它从缓存中移除
Resource<?> cached = cache.remove(key);
final EngineResource result;
if (cached == null) {
result = null;
} else if (cached instanceof EngineResource) {
result = (EngineResource) cached;
// 其他类型
} else {
result = new EngineResource(cached, true /*isCacheable*/);
}
return result;
}
// 是从activeResources这个HashMap当中取值的
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;
}
}
总结一下,Engine # load()如果能从内存缓存当中读取到要加载的图片,那么就直接进行回调,如果读取不到的话,才会开启线程执行后面的图片加载逻辑。
内存缓存写入
在图片加载完成后,EngineJob的onResourceReady()中通过Handler发送消息将执行逻辑切回到主线程当中,然后执行handleResultOnMainThread()方法。
EngineJob # handleResultOnMainThread()
private void handleResultOnMainThread() {
if (isCancelled) {
resource.recycle();
return;
} else if (cbs.isEmpty()) {
throw new IllegalStateException("Received a resource without any callbacks to notify");
}
// 这里通过EngineResourceFactory构建出一个包含图片资源的engineResource对象
engineResource = engineResourceFactory.build(resource, isCacheable);
hasResource = true;
engineResource.acquire();
// 将这个engineResource对象传入,放进弱引用缓存中
listener.onEngineJobComplete(key, engineResource);
for (ResourceCallback cb : cbs) {
if (!isInIgnoredCallbacks(cb)) {
engineResource.acquire();
// 不是此类中的
cb.onResourceReady(engineResource);
}
}
engineResource.release();
}
Engine # onEngineJobComplete()
Engine实现了EngineJobListener接口
@SuppressWarnings("unchecked")
@Override
public void onEngineJobComplete(Key key, EngineResource<?> resource) {
Util.assertMainThread();
// A null resource indicates that the load failed, usually due to an exception.
if (resource != null) {
resource.setResourceListener(key, this);
if (resource.isCacheable()) {
// 把回调过来的EngineResource放进弱引用缓存中
activeResources.put(key, new ResourceWeakReference(key, resource, getReferenceQueue()));
}
}
// TODO: should this check that the engine job is still current?
jobs.remove(key);
}
这里只是弱引用缓存,还有LruCache缓存在哪里写入?
EngineResource中的引用机制
刚才的handleResultOnMainThread()方法有调用EngineResource的acquire()和release()方法。
其实EngineResource是用一个acquired变量用来记录图片被引用的次数,调用acquire()方法会让变量加1,调用release()方法会让变量减1
class EngineResource<Z> implements Resource<Z> {
private int acquired;
...
void acquire() {
if (isRecycled) {
throw new IllegalStateException("Cannot acquire a recycled resource");
}
if (!Looper.getMainLooper().equals(Looper.myLooper())) {
throw new IllegalThreadStateException("Must call acquire on the main thread");
}
++acquired;
}
void release() {
if (acquired <= 0) {
throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
}
if (!Looper.getMainLooper().equals(Looper.myLooper())) {
throw new IllegalThreadStateException("Must call release on the main thread");
}
if (--acquired == 0) {
listener.onResourceReleased(key, this);
}
}
}
当acquired变量大于0时,说明图片正在使用中,也就应该放到弱引用缓存当中。而经过release()之后,如果acquired变量等于0了,说明图片已经不再被使用了,此时会调用listener的onResourceReleased()来释放资源(listener就是Engine)
Engine # onResourceReleased()
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
private final MemoryCache cache;
private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
...
@Override
public void onResourceReleased(Key cacheKey, EngineResource resource) {
Util.assertMainThread();
activeResources.remove(cacheKey);
if (resource.isCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
...
}
这里首先会将缓存图片从activeResources中移除,然后再将它put到LruResourceCache当中。
实现了正在使用中的图片使用弱引用来进行缓存,不在使用中的图片使用LruCache来进行缓存。
硬盘缓存
diskCacheStrategy()方法基本上就是Glide硬盘缓存功能的一切,它可以接收四种参数:
- DiskCacheStrategy.NONE: 表示不缓存任何内容。
- DiskCacheStrategy.SOURCE: 表示只缓存原始图片。
- DiskCacheStrategy.RESULT: 表示只缓存转换过后的图片(默认选项)。
- DiskCacheStrategy.ALL : 表示既缓存原始图片,也缓存转换过后的图片。
当我们使用Glide去加载一张图片的时候,Glide默认并不会将原始图片展示出来,而是会对图片进行压缩和转换(我们会在后面学习这方面的内容)。总之就是经过种种一系列操作之后得到的图片,就叫转换过后的图片。而Glide默认情况下在硬盘缓存的就是转换过后的图片(通过调用diskCacheStrategy()方法可以改变这一默认行为)
硬盘缓存的实现
也是使用的LruCache算法,Google还提供了一个现成的工具类DiskLruCache,而Glide是使用的自己编写的DiskLruCache工具类,但是基本的实现原理都是差不多的。
硬盘缓存的读取
Glide开启线程来加载图片后会执行EngineRunnable的run()方法,run()方法中又会调用一个decode()方法。
EngineRunnable # decode()
默认情况下Glide会优先从缓存当中读取
private Resource<?> decode() throws Exception {
if (isDecodingFromCache()) {
// 从硬盘缓存当中读取图片
return decodeFromCache();
} else {
// 读取原始图片
return decodeFromSource();
}
}
EngineRunnable # decodeFromCache()
会先去调用DecodeJob的decodeResultFromCache()方法来获取缓存,如果获取不到,会再调用decodeSourceFromCache()方法获取缓存,这两个方法的区别其实就是DiskCacheStrategy.RESULT和DiskCacheStrategy.SOURCE这两个参数的区别。
private Resource<?> decodeFromCache() throws Exception {
Resource<?> result = null;
try {
result = decodeJob.decodeResultFromCache();
} catch (Exception e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Exception decoding result from cache: " + e);
}
}
if (result == null) {
result = decodeJob.decodeSourceFromCache();
}
return result;
}
来看一下DecodeJob的这两个方法:
public Resource<Z> decodeResultFromCache() throws Exception {
if (!diskCacheStrategy.cacheResult()) {
return null;
}
long startTime = LogTime.getLogTime();
Resource<T> transformed = loadFromCache(resultKey);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Decoded transformed from cache", startTime);
}
startTime = LogTime.getLogTime();
Resource<Z> result = transcode(transformed);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Transcoded transformed from cache", startTime);
}
return result;
}
/**
* Returns a transformed and transcoded resource decoded from source data in the disk cache, or null if no such
* resource exists.
*
* @throws Exception
*/
public Resource<Z> decodeSourceFromCache() throws Exception {
if (!diskCacheStrategy.cacheSource()) {
return null;
}
long startTime = LogTime.getLogTime();
Resource<T> decoded = loadFromCache(resultKey.getOriginalKey());
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Decoded source from cache", startTime);
}
return transformEncodeAndTranscode(decoded);
}
都是调用了loadFromCache()方法从缓存当中读取数据,但是传入的参数不一样,一个传入的是resultKey,另外一个却又调用了resultKey的getOriginalKey()方法(Glide的缓存Key是由10个参数共同组成的,包括图片的width、height等等。但如果我们是缓存的原始图片,其实并不需要这么多的参数,因为不用对图片做任何的变化)
- 如果是decodeResultFromCache()方法就直接将数据解码并返回
- 如果是decodeSourceFromCache()方法,还要调用一下transformEncodeAndTranscode()方法先将数据转换一下再解码并返回。
// EngineKey
public Key getOriginalKey() {
if (originalKey == null) {
originalKey = new OriginalKey(id, signature);
}
return originalKey;
}
这里忽略了绝大部分的参数,只使用了id和signature这两个参数来构成缓存Key。而signature参数绝大多数情况下都是用不到的,因此基本上可以说就是由id(也就是图片url)来决定的Original缓存Key。
DecodeJob # loadFromCache()
private Resource<T> loadFromCache(Key key) throws IOException {
// getDiskCache()方法获取到的就是Glide自己编写的DiskLruCache工具类的实例
// 调用它的get()方法并把缓存Key传入,得到硬盘缓存的文件
File cacheFile = diskCacheProvider.getDiskCache().get(key);
if (cacheFile == null) {
return null;
}
Resource<T> result = null;
try {
// 解码成Resource对象后返回
result = loadProvider.getCacheDecoder().decode(cacheFile, width, height);
} finally {
if (result == null) {
diskCacheProvider.getDiskCache().delete(key);
}
}
return result;
}
硬盘缓存的写入
在没有缓存的情况下,会调用decodeFromSource()方法来读取原始图片。里面调用DecodeJob # decodeFromSource()
DecodeJob # decodeFromSource()
public Resource<Z> decodeFromSource() throws Exception {
// 解析原图片
Resource<T> decoded = decodeSource();//
// 对图片进行转换和转码的
return transformEncodeAndTranscode(decoded);
}
DecodeJob # decodeSource()
private Resource<T> decodeSource() throws Exception {
Resource<T> decoded = null;
try {
long startTime = LogTime.getLogTime();
// 读取图片数据InputStream
final A data = fetcher.loadData(priority);
if (isCancelled) {
return null;
}
// 对图片进行解码
decoded = decodeFromSourceData(data);
} finally {
fetcher.cleanup();
}
return decoded;
}
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);
// 获取DiskLruCache实例,接着调用它的put()方法就可以写入硬盘缓存了(此时缓存的是原始图片)
diskCacheProvider.getDiskCache().put(resultKey.getOriginalKey(), writer);
startTime = LogTime.getLogTime();
Resource<T> result = loadFromCache(resultKey.getOriginalKey());
return result;
}
看看转换过后的图片缓存是怎么写入的,分析一下transformEncodeAndTranscode()
DecodeJob # transformEncodeAndTranscode()
private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {
long startTime = LogTime.getLogTime();
Resource<T> transformed = transform(decoded);
// 将转换过后的图片写入到硬盘缓存中
writeTransformedToCache(transformed); //
startTime = LogTime.getLogTime();
Resource<Z> result = transcode(transformed);
return result;
}
private void writeTransformedToCache(Resource<T> transformed) {
if (transformed == null || !diskCacheStrategy.cacheResult()) {
return;
}
long startTime = LogTimefffff.getLogTime();
SourceWriter<Resource<T>> writer = new SourceWriter<Resource<T>oadProvider.getEncoder(), transformed);
// 调用的同样是DiskLruCache实例的put()方法,不过这里用的缓存Key是resultKey。
diskCacheProvider.getDiskCache().put(resultKey, writer);
}
Glide重写了LruCache,LruCache内部主要靠一个LinkedHashMap来存储缓存,这里使用LinkedHashMap而不使用普通的HashMap正是看中了它的顺序性,即LinkedHashMap中元素的存储顺序就是我们存入的顺序,而HashMap则无法保证这一点。LruCache是如何保证在缓存大于最大缓存大小之后移除的就是最近最少使用的元素呢?关键在于 trimToSize(int maxSize) 这个方法内部,在它的内部开启了一个循环,遍历LinkedHashMap,删除顶部的(也就是最先添加的)元素,直到当前缓存大小小于最大缓存,或LinkedHashMap为空。
为什么要加入弱引用缓存?
它存放的是正在使用的图片资源对象,数据结构为 HashMap<Key, WeakReference<EngineResource<?>>>。
不同于Lru算法策略的是,该缓存是无容量大小限制的,内部用引用计数来确定是否被外界正在使用,当引用为0时会从该缓存中移除,加入到内存缓存或者BitmapPool中。该缓存的作用是保护正在使用的资源并复用。如果没有这级缓存,只有LruResourceCache那么当LruCache缓存达到容量上限时有可能移除掉正在使用的图片资源,当应用中另外一个地方需要同时显示同样的图片资源时Glide将在内存中找不到这个资源对象则又会重建一个新的资源对象。
BitmapPool
是Glide中对Bitmap复用进行统一管理的接口,原则上所有需要创建Bitmap的操作,都需要经过它来进行获取。它的实现类有BitmapPoolAdapter
和LruBitmapPool
两个
- BitmapPoolAdapter 是一个空壳子,根本没有做实际意义上的缓存操作
- LruBitmapPool 采用策略模式,它自身不处理具体逻辑,真正的逻辑在LruPoolStrategy中
LruBitmapPool
是策略的执行者,也是缓存大小的控制者
public class LruBitmapPool implements BitmapPool {
private static final String TAG = "LruBitmapPool";
private static final Bitmap.Config DEFAULT_CONFIG = Bitmap.Config.ARGB_8888;
private final LruPoolStrategy strategy;
private final Set<Bitmap.Config> allowedConfigs;
private final int initialMaxSize;
private final BitmapTracker tracker;
private int maxSize;
private int currentSize;
private int hits;
private int misses;
private int puts;
private int evictions;
// Exposed for testing only.
LruBitmapPool(int maxSize, LruPoolStrategy strategy, Set<Bitmap.Config> allowedConfigs) {
this.initialMaxSize = maxSize;
this.maxSize = maxSize;
this.strategy = strategy;
this.allowedConfigs = allowedConfigs;
this.tracker = new NullBitmapTracker();
}
public LruBitmapPool(int maxSize) {
this(maxSize, getDefaultStrategy(), getDefaultAllowedConfigs());
}
public LruBitmapPool(int maxSize, Set<Bitmap.Config> allowedConfigs) {
this(maxSize, getDefaultStrategy(), allowedConfigs);
}
@Override
public int getMaxSize() {
return maxSize;
}
@Override
public synchronized void setSizeMultiplier(float sizeMultiplier) {
maxSize = Math.round(initialMaxSize * sizeMultiplier);
evict();
}
@Override
public synchronized boolean put(Bitmap bitmap) {
if (bitmap == null) {
throw new NullPointerException("Bitmap must not be null");
}
// 如果Bitmap不是Mutable或者bitmap尺寸大于最大尺寸或者bitmap.Config不在集合里(可表示每个像素占用字节数)
if (!bitmap.isMutable() || strategy.getSize(bitmap) > maxSize || !allowedConfigs.contains(bitmap.getConfig())) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Reject bitmap from pool"
+ ", bitmap: " + strategy.logBitmap(bitmap)
+ ", is mutable: " + bitmap.isMutable()
+ ", is allowed config: " + allowedConfigs.contains(bitmap.getConfig()));
}
return false;
}
final int size = strategy.getSize(bitmap);
// 放入LruCache
strategy.put(bitmap);
tracker.add(bitmap);
puts++;
currentSize += size;
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Put bitmap in pool=" + strategy.logBitmap(bitmap));
}
dump();
evict();
return true;
}
private void evict() {
trimToSize(maxSize);
}
@Override
public synchronized Bitmap get(int width, int height, Bitmap.Config config) {
Bitmap result = getDirty(width, height, config);
if (result != null) {
// Bitmaps in the pool contain random data that in some cases must be cleared for an image to be rendered
// correctly. we shouldn't force all consumers to independently erase the contents individually, so we do so
// here. See issue #131
// 池中的位图包含随机数据,在某些情况下,必须清除这些数据才能正确渲染图像
result.eraseColor(Color.TRANSPARENT);
}
return result;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
@Override
public synchronized Bitmap getDirty(int width, int height, Bitmap.Config config) {
// Config will be null for non public config types, which can lead to transformations naively passing in
// null as the requested config here. See issue #194.
final Bitmap result = strategy.get(width, height, config != null ? config : DEFAULT_CONFIG);
if (result == null) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Missing bitmap=" + strategy.logBitmap(width, height, config));
}
misses++;
} else {
// 命中
hits++;
currentSize -= strategy.getSize(result);
tracker.remove(result);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
result.setHasAlpha(true);
}
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Get bitmap=" + strategy.logBitmap(width, height, config));
}
dump();
return result;
}
@Override
public void clearMemory() {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "clearMemory");
}
trimToSize(0);
}
@SuppressLint("InlinedApi")
@Override
public void trimMemory(int level) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "trimMemory, level=" + level);
}
if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
clearMemory();
} else if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
trimToSize(maxSize / 2);
}
}
// 将缓存整理到size大小以内
private synchronized void trimToSize(int size) {
while (currentSize > size) {
final Bitmap removed = strategy.removeLast();
// TODO: This shouldn't ever happen, see #331.
if (removed == null) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Size mismatch, resetting");
dumpUnchecked();
}
currentSize = 0;
return;
}
tracker.remove(removed);
currentSize -= strategy.getSize(removed);
// 将淘汰的bitmap回收
removed.recycle();
evictions++;
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Evicting bitmap=" + strategy.logBitmap(removed));
}
dump();
}
}
private void dump() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
dumpUnchecked();
}
}
private void dumpUnchecked() {
Log.v(TAG, "Hits=" + hits
+ ", misses=" + misses
+ ", puts=" + puts
+ ", evictions=" + evictions
+ ", currentSize=" + currentSize
+ ", maxSize=" + maxSize
+ "\nStrategy=" + strategy);
}
private static LruPoolStrategy getDefaultStrategy() {
final LruPoolStrategy strategy;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// 高版本的inBitmap限制没这麽严格,在尺寸这里是放开了,只要内存大小不小于需求就行
strategy = new SizeConfigStrategy();
} else {
strategy = new AttributeStrategy();
}
return strategy;
}
// 获取能够缓存的config
private static Set<Bitmap.Config> getDefaultAllowedConfigs() {
Set<Bitmap.Config> configs = new HashSet<Bitmap.Config>();
configs.addAll(Arrays.asList(Bitmap.Config.values()));
if (Build.VERSION.SDK_INT >= 19) {
configs.add(null);
}
return Collections.unmodifiableSet(configs);
}
private interface BitmapTracker {
void add(Bitmap bitmap);
void remove(Bitmap bitmap);
}
@SuppressWarnings("unused")
// Only used for debugging
private static class ThrowingBitmapTracker implements BitmapTracker {
private final Set<Bitmap> bitmaps = Collections.synchronizedSet(new HashSet<Bitmap>());
@Override
public void add(Bitmap bitmap) {
if (bitmaps.contains(bitmap)) {
throw new IllegalStateException("Can't add already added bitmap: " + bitmap + " [" + bitmap.getWidth()
+ "x" + bitmap.getHeight() + "]");
}
bitmaps.add(bitmap);
}
@Override
public void remove(Bitmap bitmap) {
if (!bitmaps.contains(bitmap)) {
throw new IllegalStateException("Cannot remove bitmap not in tracker");
}
bitmaps.remove(bitmap);
}
}
private static class NullBitmapTracker implements BitmapTracker {
@Override
public void add(Bitmap bitmap) {
// Do nothing.
}
@Override
public void remove(Bitmap bitmap) {
// Do nothing.
}
}
}
从Android 3.0 (API Level 11)开始,引进了BitmapFactory.Options.inBitmap字段。如果这个值被设置了,decode方法会在加载内容的时候去reuse已经存在的bitmap. 这意味着bitmap的内存是被reused的,这样可以提升性能, 并且减少了内存的allocation与de-allocation.
Glide的优点
- 不只是图片缓存,还支持Gif,WebP,缩略图,甚至Video
- 支持优先级处理
- 生命周期集成
- Picasso只会缓存原始尺寸的图片,而Glide缓存的是多种规格(会根据ImageView的大小)
- 内存开销小,默认的 Bitmap 格式是 RGB_565 格式,而 Picasso 默认的是 ARGB_8888 格式(图片质量高),这个内存开销要小一半
LruCache
其中对象列表的排列方式是按照访问顺序实现的,即一直没访问的对象,将放在队头,即将被淘汰。而最近访问的对象将放在队尾,最后被淘汰。LinkedHashMap是由数组+双向链表的数据结构来实现的,其中双向链表的结构可以实现访问顺序和插入顺序。
LinkedHashMap的构造函数:
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
其中accessOrder设置为true则为访问顺序,为false就是插入顺序。(这里一般设置为true)
put()
@Nullable
public final V put(@NonNull K key, @NonNull V value) {
if (key != null && value != null) {
Object previous;
synchronized(this) {
++this.putCount;
this.size += this.safeSizeOf(key, value);
// 存进LinkedHashMap中
previous = this.map.put(key, value);
if (previous != null) {
// 减掉之前的value大小
this.size -= this.safeSizeOf(key, previous);
}
}
if (previous != null) {
this.entryRemoved(false, key, previous, value);
}
this.trimToSize(this.maxSize);
return previous;
} else {
throw new NullPointerException("key == null || value == null");
}
}
get()
@Nullable
public final V get(@NonNull K key) {
if (key == null) {
throw new NullPointerException("key == null");
} else {
Object mapValue;
synchronized(this) {
mapValue = this.map.get(key);
if (mapValue != null) {
++this.hitCount;
return mapValue;
}
// 未命中
++this.missCount;
}
// 未命中 尝试利用create方法创建对象
create需要自己实现,若未实现则返回null
V createdValue = this.create(key);
if (createdValue == null) {
return null;
} else {
// 创建了新对象之后,再将其添加进map中,与之前put方法逻辑基本相同
synchronized(this) {
++this.createCount;
mapValue = this.map.put(key, createdValue);
if (mapValue != null) {
this.map.put(key, mapValue);
} else {
this.size += this.safeSizeOf(key, createdValue);
}
}
if (mapValue != null) {
this.entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
this.trimToSize(this.maxSize);
return createdValue;
}
}
}
}
trimToSize()
判断缓存是否已满,满了就删除
public void trimToSize(int maxSize) {
while(true) {
Object key;
Object value;
synchronized(this) {
if (this.size < 0 || this.map.isEmpty() && this.size != 0) {
throw new IllegalStateException(this.getClass().getName() + ".sizeOf() is reporting inconsistent results!");
}
if (this.size <= maxSize || this.map.isEmpty()) {
return;
}
// 从队头删除
Entry<K, V> toEvict = (Entry)this.map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
this.map.remove(key);
this.size -= this.safeSizeOf(key, value);
// 回收次数
++this.evictionCount;
}
this.entryRemoved(true, key, value, (Object)null);
}
}