在上一篇追寻源码,梳理了一下Fresco加载中涉及到的一些目标,以及在这个架构中怎么完结恳求。然后剖析了网络恳求的加载进程,剖析了ProducerSequence的组成部分,一期完好的恳求流程需求经过如下目标和作业:

  1. BitmapMemoryCacheGetProducer,图片缓存读取

  2. ThreadHandoffProducer,线程切换

  3. BitmapMemoryCacheKeyMultiplexProducer,多路复用,相同恳求进行合并

  4. BitmapMemoryCacheProducer,图片缓存

  5. ResizeAndRotateProducer,图片调整

  6. AddImageTransformMetaDataProducer,元数据解码

  7. EncodedCacheKeyMultiplexProducer,元数据多路复用

  8. EncodedMemoryCacheProducer,元数据缓存

  9. DiskCacheReadProducer,磁盘缓存读取

  10. DiskCacheWriteProducer,磁盘缓存写入

  11. WebpTranscodeProducer,webp转码

  12. NetworkFetchProducer,网络恳求

其间一共具有三层缓存

  1. 第一层Bitmap缓存,Bitmap缓存存储Bitmap目标,这些Bitmap目标能够立即用来显示,在线程切换之前就读缓存,缓存在内存傍边,在后台会被清掉,Bitmap相对于元数据会大许多,参考之前的Bitmap相关知识

  2. 第二层元数据缓存,元数据缓存存储原始紧缩格式图片如png、jpg,这些缓存在运用时需求先解码成bitmap,运用会再次缓存到第一层缓存,缓存在内存中,在后台会被清掉

  3. 第三层元数据缓存,与第二层的缓存数据完全一致,运用时需求解码,运用会再次缓存到第一层和第二层缓存中,缓存在磁盘中,在后台不回被铲除

BitmapMemoryCacheProducer

内存缓存是一种用于存储图片数据的临时存储空间,能够快速地拜访和加载图片资源,进步图片加载的效率和功能。内存缓存通常存储在RAM中,因而能够快速地读取和写入数据。

介绍

Fresco的缓存架构中,前两层都是运用的内存缓存,分别针对的是Bitmap数据和元数据:

  • Bitmap缓存涉及两个Producer,BitmapMemoryCacheGetProducer和BitmapMemoryCacheProducer。

    • BitmapMemoryCacheGetProducer承继自BitmapMemoryCacheProducer,制止了其写缓存的才能。所以逻辑还是在BitmapMemoryCacheProducer中。
    • 当图片从网络或本地加载后,经过解码生成位图后,BitmapMemoryCacheProducer会将这些位图数据存储到内存缓存中。下次再次加载相同的图片时,能够直接从内存缓存中读取位图数据,避免从头解码,进步图片加载的速度和效率。
  • 元数据缓存EncodedMemoryCacheProducer,担任将原始的编码数据存储到编码内存缓存中。当图片从网络或本地加载后,未经过解码的编码数据会被EncodedMemoryCacheProducer存储到编码内存缓存中。这样在需求从头加载图片时,能够直接从编码内存缓存中读取原始的编码数据,再解码生成位图,避免从头下载图片,进步加载速度。

从功能上能够看出,BitmapMemoryCacheProducer和EncodedMemoryCacheProducer除了针对的目标不同之外,逻辑是完全相同的。

代码流程

  1. 当BitmapMemoryCacheProducer的produceResults办法被调用时,首先从ProducerContext中获取到对应的CacheKey。
  2. 接着经过CacheKey从内存缓存中查找是否有对应的位图数据。假如内存缓存中有对应的位图数据,则直接将数据回来给Consumer,并关闭CloseableReference。
  3. 假如内存缓存中没有对应的位图数据,则调用下一个生产者(mInputProducer)的produceResults办法,一起将一个包装过的Consumer传入其间。
  4. 包装过的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算法会依据最近拜访的次序来淘汰最少运用的数据,以坚持缓存巨细在一定范围内。

存储办法和目标

  1. 存储办法

    1. mExclusiveEntries存储的是那些不被任何客户端运用的缓存条目,这些是可被整理的、闲暇的缓存条目,因而这些条目能够被驱逐出缓存,当一个条目不再被运用时,会被移动到mExclusiveEntries
    2. mCachedEntries则是存储所有缓存条目的当地,包含那些被符号为独占的条目和普通的缓存条目。

final CountingLruMap<K, Entry<K, V>> mExclusiveEntries;
final CountingLruMap<K, Entry<K, V>> mCachedEntries;
  1. 存储目标

    1. key是条目的键,用于唯一标识该条目在缓存中的方位。
    2. valueRef是一个CloseableReference类型的成员变量,用于存储条目对应的值的引证。
    3. clientCount表明引证该条目值的客户端数量,即有多少个客户端正在运用这个值。
    4. isOrphan表明该条目是否孤立,孤立的条目意味着这个条目不再被缓存办理器追寻。
class Entry<K, V> {
  public final K key;
  public final CloseableReference<V> valueRef;
  public int clientCount;
  public boolean isOrphan;
}

读取

从缓存中获取指定key对应的值,假如有缓存回来一个引证,假如没有缓存回来空。

  1. 从mExclusiveEntries中移除对应的Entry。
  2. 从mCachedEntries中取对应的Entry。
  3. 假如获取到了Entry,则调用newClientReference()创立一个新的CloseableReference引证。
  4. 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引证,供给外面的调用方单独运用,运用完结后对引证进行开释,经过这样完成了一对多的办理办法。

  1. newClientReference办法用于创立一个新的引证

    1. 首先经过increaseClientCount()添加计数,表明有一个新的引证该条目值。
    2. 然后创立一个CloseableReference,办理对条目值的引证,并在引证不再需求时开释资源。
    3. 当引证需求开释时,会调用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);
        }
      });
}
  1. releaseClientReference办法用于开释引证,

    1. 如减少客户端计数、或许将条目添加到独占调集、关闭旧的引证等。
    2. 首先,减少了条目的客户端计数,表明有一个客户端不再引证该条目值。
    3. maybeAddToExclusives(),假如计数降为0可是没有孤立,表明条目不再被运用时,会被移动到mExclusiveEntries。
    4. referenceToClose(),计数降为0并且现已孤立,发生在缓存现已由于其他状况铲除去的状况下,并且当时开释的现已是条目最终一个引证,在这儿将它开释掉。
    5. 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;
}

逐出

逐出操作管帐算出需求整理的最大数量和巨细,计算需求整理的旧条目。并且安全地开释资源。

  1. maybeEvictEntries()是条目逐出的进口,前面有许多当地调用了它,流程如下

    1. 计算出需求整理的最大数量和巨细,约束在规则的最大整理行列条目数和缓存巨细范围内。
    2. 调用trimExclusivelyOwnedEntries()办法来获取需求整理的旧条目。
    3. 调用makeOrphans()办法来将这些旧条目符号为孤立状况,不再被其他条目引证。
    4. 开释同步锁后,调用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);
}
  1. trimExclusivelyOwnedEntries

    1. 判断当时mExclusiveEntries中的条目数量和巨细是否小于等于传入的count和size,假如是,则不需求进行裁剪操作,直接回来null。
    2. 进入一个while循环,判断当时mExclusiveEntries中的条目数量和巨细是否大于传入的count和size,假如是,则继续裁剪操作。
    3. 获取mExclusiveEntries中第一个条目的key,并从mExclusiveEntries和mCachedEntries中移除该条目,将其添加到oldEntries中。
    4. 循环直到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内存缓存的架构和作业原理,我们能够更好地优化和办理应用程序的内存运用,为用户供给更流畅的图片加载和展现体会。