在上一篇追寻源码,梳理了一下Fresco加载中涉及到的一些目标,以及在这个架构中怎么完结恳求。然后剖析了网络恳求的加载进程,剖析了ProducerSequence的组成部分,一期完好的恳求流程需求经过如下目标和作业:
-
BitmapMemoryCacheGetProducer,图片缓存读取
-
ThreadHandoffProducer,线程切换
-
BitmapMemoryCacheKeyMultiplexProducer,多路复用,相同恳求进行合并
-
BitmapMemoryCacheProducer,图片缓存
-
ResizeAndRotateProducer,图片调整
-
AddImageTransformMetaDataProducer,元数据解码
-
EncodedCacheKeyMultiplexProducer,元数据多路复用
-
EncodedMemoryCacheProducer,元数据缓存
-
DiskCacheReadProducer,磁盘缓存读取
-
DiskCacheWriteProducer,磁盘缓存写入
-
WebpTranscodeProducer,webp转码
-
NetworkFetchProducer,网络恳求
其间一共具有三层缓存
-
第一层Bitmap缓存,Bitmap缓存存储Bitmap目标,这些Bitmap目标能够立即用来显示,在线程切换之前就读缓存,缓存在内存傍边,在后台会被清掉,Bitmap相对于元数据会大许多,参考之前的Bitmap相关知识
-
第二层元数据缓存,元数据缓存存储原始紧缩格式图片如png、jpg,这些缓存在运用时需求先解码成bitmap,运用会再次缓存到第一层缓存,缓存在内存中,在后台会被清掉
-
第三层元数据缓存,与第二层的缓存数据完全一致,运用时需求解码,运用会再次缓存到第一层和第二层缓存中,缓存在磁盘中,在后台不回被铲除
BitmapMemoryCacheProducer
内存缓存是一种用于存储图片数据的临时存储空间,能够快速地拜访和加载图片资源,进步图片加载的效率和功能。内存缓存通常存储在RAM中,因而能够快速地读取和写入数据。
介绍
Fresco的缓存架构中,前两层都是运用的内存缓存,分别针对的是Bitmap数据和元数据:
-
Bitmap缓存涉及两个Producer,BitmapMemoryCacheGetProducer和BitmapMemoryCacheProducer。
- BitmapMemoryCacheGetProducer承继自BitmapMemoryCacheProducer,制止了其写缓存的才能。所以逻辑还是在BitmapMemoryCacheProducer中。
- 当图片从网络或本地加载后,经过解码生成位图后,BitmapMemoryCacheProducer会将这些位图数据存储到内存缓存中。下次再次加载相同的图片时,能够直接从内存缓存中读取位图数据,避免从头解码,进步图片加载的速度和效率。
-
元数据缓存EncodedMemoryCacheProducer,担任将原始的编码数据存储到编码内存缓存中。当图片从网络或本地加载后,未经过解码的编码数据会被EncodedMemoryCacheProducer存储到编码内存缓存中。这样在需求从头加载图片时,能够直接从编码内存缓存中读取原始的编码数据,再解码生成位图,避免从头下载图片,进步加载速度。
从功能上能够看出,BitmapMemoryCacheProducer和EncodedMemoryCacheProducer除了针对的目标不同之外,逻辑是完全相同的。
代码流程
- 当BitmapMemoryCacheProducer的produceResults办法被调用时,首先从ProducerContext中获取到对应的CacheKey。
- 接着经过CacheKey从内存缓存中查找是否有对应的位图数据。假如内存缓存中有对应的位图数据,则直接将数据回来给Consumer,并关闭CloseableReference。
- 假如内存缓存中没有对应的位图数据,则调用下一个生产者(mInputProducer)的produceResults办法,一起将一个包装过的Consumer传入其间。
- 包装过的Consumer当从下一个生产者获取到新的成果时,将成果存储到内存缓存中,并继续传递成果给原始的Consumer。
其间缓存获取和存储分别是经过MemoryCache的get和put办法完成。
public class BitmapMemoryCacheProducer implements Producer<CloseableReference<CloseableImage>> {
private final Producer<CloseableReference<CloseableImage>> mInputProducer;
private final MemoryCache<CacheKey, CloseableImage> mMemoryCache;
public BitmapMemoryCacheProducer(
Producer<CloseableReference<CloseableImage>> inputProducer,
MemoryCache<CacheKey, CloseableImage> memoryCache) {
mInputProducer = Preconditions.checkNotNull(inputProducer);
mMemoryCache = Preconditions.checkNotNull(memoryCache);
}
@Override
public void produceResults(Consumer<CloseableReference<CloseableImage>> consumer, ProducerContext context) {
//从ProducerContext中获取到对应的CacheKey
final ImageRequest imageRequest = producerContext.getImageRequest();
final Object callerContext = producerContext.getCallerContext();
final CacheKey cacheKey = mCacheKeyFactory.getBitmapCacheKey(imageRequest, callerContext);
// 从内存缓存中查找是否有对应的位图数据
CloseableReference<CloseableImage> closeableImage = mMemoryCache.get(cacheKey);
if (closeableImage != null) {
// 假如内存缓存中有对应的位图数据,则直接回来给consumer
consumer.onProgressUpdate(1f);
consumer.onNewResult(closeableImage, true);
closeableImage.close();
} else {
// 假如内存缓存中没有对应的位图数据,则继续向下一个生产者恳求数据
Consumer<CloseableReference<CloseableImage>> wrappedConsumer =
wrapConsumer(
consumer, cacheKey, producerContext.getImageRequest().isMemoryCacheEnabled());
mInputProducer.produceResults(wrappedConsumer, context);
}
}
private Consumer<CloseableReference<CloseableImage>> wrapConsumer(final Consumer<CloseableReference<CloseableImage>> consumer, final CacheKey cacheKey) {
return new DelegatingConsumer<CloseableReference<CloseableImage>, CloseableReference<CloseableImage>>(consumer) {
@Override
public void onNewResultImpl(CloseableReference<CloseableImage> result, boolean isLast) {
// 当从下一个生产者获取到新的成果时,将成果存储到内存缓存中
if (isLast) {
if (result != null) {
newCachedResult = mMemoryCache.cache(cacheKey, newResult);
}
}
// 继续传递成果给Consumer
getConsumer()
.onNewResult((newCachedResult != null) ? newCachedResult : newResult, status);
}
};
}
}
LruCountingMemoryCache
介绍
MemoryCache是Fresco中非常重要的一个组件,它能够有用地办理内存中的图片缓存,进步图片加载的效率和功能。MemoryCache支撑装备不同的缓存战略,包含缓存的巨细约束、缓存的有用期、缓存的整理战略等。能够依据自己的需求和场景来调整MemoryCache的装备。
LruCountingMemoryCache是MemoryCache一个首要完成。是一个结合了LRU算法和计数功能的内存缓存类,LRU算法会依据最近拜访的次序来淘汰最少运用的数据,以坚持缓存巨细在一定范围内。
存储办法和目标
-
存储办法
- mExclusiveEntries存储的是那些不被任何客户端运用的缓存条目,这些是可被整理的、闲暇的缓存条目,因而这些条目能够被驱逐出缓存,当一个条目不再被运用时,会被移动到mExclusiveEntries
- mCachedEntries则是存储所有缓存条目的当地,包含那些被符号为独占的条目和普通的缓存条目。
final CountingLruMap<K, Entry<K, V>> mExclusiveEntries;
final CountingLruMap<K, Entry<K, V>> mCachedEntries;
-
存储目标
- key是条目的键,用于唯一标识该条目在缓存中的方位。
- valueRef是一个CloseableReference类型的成员变量,用于存储条目对应的值的引证。
- clientCount表明引证该条目值的客户端数量,即有多少个客户端正在运用这个值。
- isOrphan表明该条目是否孤立,孤立的条目意味着这个条目不再被缓存办理器追寻。
class Entry<K, V> {
public final K key;
public final CloseableReference<V> valueRef;
public int clientCount;
public boolean isOrphan;
}
读取
从缓存中获取指定key对应的值,假如有缓存回来一个引证,假如没有缓存回来空。
- 从mExclusiveEntries中移除对应的Entry。
- 从mCachedEntries中取对应的Entry。
- 假如获取到了Entry,则调用newClientReference()创立一个新的CloseableReference引证。
- maybeUpdateCacheParams()和maybeEvictEntries(),第一个或许会依据缓存的巨细、存储战略等因从来更新缓存的相关参数。第二个或许逐出不必要的缓存项,由于上一步或许更改了缓存参数,导致需求从头计算。
@Nullable
public CloseableReference<V> get(final K key) {
Entry<K, V> oldExclusive;
CloseableReference<V> clientRef = null;
synchronized (this) {
oldExclusive = mExclusiveEntries.remove(key);
Entry<K, V> entry = mCachedEntries.get(key);
if (entry != null) {
clientRef = newClientReference(entry);
}
}
maybeUpdateCacheParams();
maybeEvictEntries();
return clientRef;
}
引证
读取缓存时回来的目标是实践目标的一个全新的引证,Entry存储的是外面传入的CloseableReference引证,而从缓存中取条目的时分,会运用这个CloseableReference引证的目标创立一个新的CloseableReference引证,供给外面的调用方单独运用,运用完结后对引证进行开释,经过这样完成了一对多的办理办法。
-
newClientReference办法用于创立一个新的引证
- 首先经过increaseClientCount()添加计数,表明有一个新的引证该条目值。
- 然后创立一个CloseableReference,办理对条目值的引证,并在引证不再需求时开释资源。
- 当引证需求开释时,会调用releaseClientReference()办法开释引证。
private synchronized CloseableReference<V> newClientReference(final Entry<K, V> entry) {
increaseClientCount(entry);
return CloseableReference.of(
entry.valueRef.get(),
new ResourceReleaser<V>() {
@Override
public void release(V unused) {
releaseClientReference(entry);
}
});
}
-
releaseClientReference办法用于开释引证,
- 如减少客户端计数、或许将条目添加到独占调集、关闭旧的引证等。
- 首先,减少了条目的客户端计数,表明有一个客户端不再引证该条目值。
- maybeAddToExclusives(),假如计数降为0可是没有孤立,表明条目不再被运用时,会被移动到mExclusiveEntries。
- referenceToClose(),计数降为0并且现已孤立,发生在缓存现已由于其他状况铲除去的状况下,并且当时开释的现已是条目最终一个引证,在这儿将它开释掉。
- maybeUpdateCacheParams()和maybeEvictEntries(),第一个或许会依据缓存的巨细、存储战略等因从来更新缓存的相关参数。第二个或许逐出不必要的缓存项,由于上一步或许更改了缓存参数,导致需求从头计算。
private void releaseClientReference(final Entry<K, V> entry) {
Preconditions.checkNotNull(entry);
boolean isExclusiveAdded;
CloseableReference<V> oldRefToClose;
synchronized (this) {
decreaseClientCount(entry);
isExclusiveAdded = maybeAddToExclusives(entry);
oldRefToClose = referenceToClose(entry);
}
CloseableReference.closeSafely(oldRefToClose);
maybeUpdateCacheParams();
maybeEvictEntries();
}
存储
将键值对缓存到内存中。
-
maybeUpdateCacheParams(),依据缓存的巨细、存储战略等因从来更新缓存的相关参数。这个跟之前几个办法有区别,放到了最前面来。
-
第二步去重操作,查找mExclusiveEntries和mCachedEntries中是否现已存储了相同的key,假如有就把它铲除去,这儿一部分操作在同步块内,一部分在外面,这是为了避免潜在的死锁状况,由于在调用close办法时或许会涉及到其他线程或者资源的开释操作,假如在持有锁的状况下调用这个办法,或许会导致死锁。下面是具体流程:
- mExclusiveEntries.remove和mCachedEntries.remove找出对应的缓存
- 假如的确从缓存中查到了值,makeOrphan()符号条目现已孤立,eferenceToClose(),计数为0并且现已孤立,在这儿将它开释掉。假如计数不为0,阐明还有运用方,等候所有运用方开释之后再开释资源
- 实践的开释代码在同步块外面
-
经过canCacheNewValue()办法判断新值是否能够缓存,假如能够,则创立一个新的Entry目标并将其放入mCachedEntries中,并经过newClientReference()办法创立客户端引证。
-
调用maybeEvictEntries()办法,或许逐出不必要的缓存项,由于办法最开始更改了缓存参数,别的还有或许插入了新的缓存值。
-
回来客户端引证clientRef,供调用者运用。假如不为空,调用方应该运用回来的引证,假如为空,调用方运用原始引证。
@Override
public @Nullable CloseableReference<V> cache(
final K key,
final CloseableReference<V> valueRef,
final @Nullable EntryStateObserver<K> observer) {
maybeUpdateCacheParams();
Entry<K, V> oldExclusive;
CloseableReference<V> oldRefToClose = null;
CloseableReference<V> clientRef = null;
synchronized (this) {
// remove the old item (if any) as it is stale now
oldExclusive = mExclusiveEntries.remove(key);
Entry<K, V> oldEntry = mCachedEntries.remove(key);
if (oldEntry != null) {
makeOrphan(oldEntry);
oldRefToClose = referenceToClose(oldEntry);
}
if (canCacheNewValue(valueRef.get())) {
Entry<K, V> newEntry = Entry.of(key, valueRef, observer);
mCachedEntries.put(key, newEntry);
clientRef = newClientReference(newEntry);
}
}
CloseableReference.closeSafely(oldRefToClose);
maybeEvictEntries();
return clientRef;
}
逐出
逐出操作管帐算出需求整理的最大数量和巨细,计算需求整理的旧条目。并且安全地开释资源。
-
maybeEvictEntries()是条目逐出的进口,前面有许多当地调用了它,流程如下
- 计算出需求整理的最大数量和巨细,约束在规则的最大整理行列条目数和缓存巨细范围内。
- 调用trimExclusivelyOwnedEntries()办法来获取需求整理的旧条目。
- 调用makeOrphans()办法来将这些旧条目符号为孤立状况,不再被其他条目引证。
- 开释同步锁后,调用maybeClose(),假如条目计数为0并且现已孤立,在这儿将它开释掉。
public void maybeEvictEntries() {
ArrayList<Entry<K, V>> oldEntries;
synchronized (this) {
int maxCount =
Math.min(
mMemoryCacheParams.maxEvictionQueueEntries,
mMemoryCacheParams.maxCacheEntries - getInUseCount());
int maxSize =
Math.min(
mMemoryCacheParams.maxEvictionQueueSize,
mMemoryCacheParams.maxCacheSize - getInUseSizeInBytes());
oldEntries = trimExclusivelyOwnedEntries(maxCount, maxSize);
makeOrphans(oldEntries);
}
maybeClose(oldEntries);
}
-
trimExclusivelyOwnedEntries
- 判断当时mExclusiveEntries中的条目数量和巨细是否小于等于传入的count和size,假如是,则不需求进行裁剪操作,直接回来null。
- 进入一个while循环,判断当时mExclusiveEntries中的条目数量和巨细是否大于传入的count和size,假如是,则继续裁剪操作。
- 获取mExclusiveEntries中第一个条目的key,并从mExclusiveEntries和mCachedEntries中移除该条目,将其添加到oldEntries中。
- 循环直到mExclusiveEntries中的条目数量和巨细都小于等于传入的count和size,然后回来oldEntries。
这儿逐出mExclusiveEntries中第一个条目的key,存入mExclusiveEntries的操作在条目被开释的时分,所以第一个条目也便是最早不被运用的条目,也便是lru战略。
private synchronized ArrayList<Entry<K, V>> trimExclusivelyOwnedEntries(int count, int size) {
// fast path without array allocation if no eviction is necessary
if (mExclusiveEntries.getCount() <= count && mExclusiveEntries.getSizeInBytes() <= size) {
return null;
}
ArrayList<Entry<K, V>> oldEntries = new ArrayList<>();
while (mExclusiveEntries.getCount() > count || mExclusiveEntries.getSizeInBytes() > size) {
K key = mExclusiveEntries.getFirstKey()
mExclusiveEntries.remove(key);
oldEntries.add(mCachedEntries.remove(key));
}
return oldEntries;
}
总结
在本文中,我们深化探讨了Fresco结构中关于内存缓存的部分,首要环绕BitmapMemoryCacheProducer和LruCountingMemoryCache展开讨论。作为Fresco结构内存办理的核心组件,这两者一起构建了Fresco内存缓存的架构。
BitmapMemoryCacheProducer作为内存缓存的生产者,担任将位图数据存储到内存缓存中,并供给给下游消费者运用。它经过高效办理内存中的位图数据,完成了内存缓存的快速读取和存储,为Fresco结构的图片加载和展现供给了重要支撑。
而LruCountingMemoryCache则是BitmapMemoryCacheProducer中的关键组件,完成了LRU算法用于办理缓存条目。经过对内部存储办法、目标引证、逐出操作等流程的具体讲解,我们深化了解了Fresco内存缓存的内部作业原理。特别是在逐出操作方面,经过裁剪旧条目来开释内存空间,保证了内存缓存的有用使用和系统的稳定性。
归纳来看,Fresco结构经过BitmapMemoryCacheProducer和LruCountingMemoryCache这样的内存办理组件,构建了一个高效、稳定的内存缓存架构。合理使用内存缓存,不仅进步了应用程序的功能和用户体会,一起也减少了内存资源的浪费和系统负担。经过深化了解和学习Fresco内存缓存的架构和作业原理,我们能够更好地优化和办理应用程序的内存运用,为用户供给更流畅的图片加载和展现体会。