Fresco(2.5.0) 以MVC的 Fresco架构入手,层层递进分析fresco的整体思路。
个人理解会将 图中的UI层进一步分为 DraweeView为View层(负责渲染),DraweeHierarchy为Model层(记录配置与数据Drawable[6]),DraweeController为Controller层(分离PorducerSequence加载缓存责任链条)
ps:MVC现在很多变种中都会有View层与Model层 交互,View层与Controller层 交互。但一般都不会有Model持有Controller。所以我会将fresco理解为MVC。
View—DraweeView 也就是我们直接接触到的具体实现类 SimpleDraweeView
。早期是继承imageView类的,后来是改成view并由draweeView自己渲染图层
继承于 View , 负责图片的显示。持有DraweeHolder (DraweeHolder中含有的model层 和 DraweeController 的controller层)。//见上图
SimpleDraweeView中,setImageUrl方法中直接调用
1 AbstractDraweeControllerBuilder.build()
以构造Controller。
至于AbstractDraweeControllerBuilder唯一继承类,是PipelineDraweeControllerBuilder。
PipelineDraweeControllerBuilder中obtainController是借助PipelineDraweeControllerFactory完成的
具体看下面的Controler—DraweeController解析
Model—DraweeHierarchy 直接用到的是GenericDraweeHierarchy ,这一层保存和管理图片的六个图层(layers),如果有overlay的话再加一层
Fresco 图片渲染 —— 六层drawable 1 2 3 4 5 6 7 public class DraweeView <DH extends DraweeHierarchy > extends ImageView { public void setController (@Nullable DraweeController draweeController) { mDraweeHolder.setController(draweeController); super .setImageDrawable(mDraweeHolder.getTopLevelDrawable()); } }
在调用SimpleDraweeView.setImageUri()
时会调用到DraweeView.setController()
,即此时是直接显示的mDraweeHolder.getTopLevelDrawable()
:
DraweeHolder.java
1 2 3 public @Nullable Drawable getTopLevelDrawable () { return mHierarchy == null ? null : mHierarchy.getTopLevelDrawable(); }
所以最终的显示的Drawable
是mHierarchy.getTopLevelDrawable()
。mHierarchy
的实现是GenericDraweeHierarchy
。mHierarchy.getTopLevelDrawable()
获取的Drawable
实际上可以理解为FadeDrawable
:
GenericDraweeHierarchy.java
1 2 3 4 5 GenericDraweeHierarchy(GenericDraweeHierarchyBuilder builder) { mFadeDrawable = new FadeDrawable(layers); Drawable maybeRoundedDrawable = WrappingUtils.maybeWrapWithRoundedOverlayColor(mFadeDrawable, mRoundingParams); mTopLevelDrawable = new RootDrawable(maybeRoundedDrawable); }
FadeDrawable
内部维护着一个Drawable
数组,它可以由一个Drawable
切换到另一个Drawable
,Drawable
的切换过程中伴有着透明度改变的动画:
1 2 3 4 5 6 7 8 9 10 11 12 13 public class FadeDrawable extends ArrayDrawable { private final Drawable[] mLayers; @Override public void draw (Canvas canvas) { ...更新Drawable的透明度 for (int i = 0 ; i < mLayers.length; i++) { drawDrawableWithAlpha(canvas, mLayers[i], mAlphas[i] * mAlpha / 255 ); } } }
主要有顶级图层,占位符图层,目标显示图层,重新加载图层,显示失败图层, 进度条图层,控制覆盖图层
DraweeHierarchy由DraweeController
直接持有的,DraweeController通过DataSource能轻易得到各个图片加载时机,因此对于不同图片显示的切换操作具体是由DraweeController
来直接操作的。
Controler—DraweeController 关键词:依赖注入(控制反转的思想)、责任链、生产者消费者、构建者
fresco中具体实现为PipelineDraweeController,集成关系:
1 public class PipelineDraweeController extends AbstractDraweeController
1 2 3 4 5 6 7 8 9 10 public abstract class AbstractDraweeController <T , INFO > implements DraweeController { public void onAttach () { } public void onDetach () { } protected void submitRequest () { } }
1 public interface DraweeController
DraweeController与DataSource直接交互,通过DataSource控制ProducterSequence以 责任链模式的思想 加载图片。
从 bitmap对象 到 未解码图片的内存缓存 到 图片的磁盘缓存 到 网络拉取
DraweeController的构造 在Fresco
中DraweeController
是通过DraweeControllerBuilder
来构造的。而DraweeControllerBuilder
在Fresco
中是以单例的形式存在的。Fresco
在初始化时会调用下面的代码:
Fresco.java
1 2 3 4 private static void initializeDrawee (Context context, @Nullable DraweeConfig draweeConfig) { sDraweeControllerBuilderSupplier = new PipelineDraweeControllerBuilderSupplier(context, draweeConfig); SimpleDraweeView.initialize(sDraweeControllerBuilderSupplier); }
所以所有的DraweeController
都是通过同一个DraweeControllerBuilder
来构造的。Fresco
每次图片加载都会对应到一个DraweeController
,一个DraweeView
的多次图片加载可以复用同一个DraweeController
:
SimpleDraweeView.java
1 2 3 4 5 6 7 8 9 public void setImageURI (Uri uri, @Nullable Object callerContext) { DraweeController controller = mControllerBuilder .setCallerContext(callerContext) .setUri(uri) .setOldController(getController()) .build(); setController(controller); }
所以一般情况下 : **一个DraweeView
对应一个DraweeController
**。由于DraweeController很重,所以使用obtainController() 回收利用并ControllerBuilder提供setOldController()传入draweeView复用DraweeController
通过DataSource发起图片加载 在前面已经说了DraweeController
是直接持有DraweeHierachy
,所以它观察到ProducerSequence
的数据变化是可以很容易更新到DraweeHierachy
(具体代码先不展示了)。那它是如何控制ProducerSequence
来加载图片的呢?其实DraweeController
并不会直接和ProducerSequence
发生关联。对于图片的加载,它直接接触的是DataSource
,由DataSource
进而来控制ProducerSequence
发起图片加载和处理流程。下面就跟随源码来看一下DraweeController
是如果通过DataSource
来控制ProducerSequence
发起图片加载和处理流程的。
DraweeController发起图片加载请求的方法是(AbstractDraweeController.java):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 protected void submitRequest () { final T closeableImage = getCachedImage(); if (closeableImage != null ) { onNewResultInternal(mId, mDataSource, closeableImage, 1.0f , true , true , true ); } mDataSource = getDataSource(); final DataSubscriber<T> dataSubscriber = new BaseDataSubscriber<T>() { @Override public void onNewResultImpl (DataSource<T> dataSource) { boolean isFinished = dataSource.isFinished(); boolean hasMultipleResults = dataSource.hasMultipleResults(); float progress = dataSource.getProgress(); T image = dataSource.getResult(); if (image != null ) { onNewResultInternal( id, dataSource, image, progress, isFinished, wasImmediate, hasMultipleResults); } else if (isFinished){ onFailureInternal(id, dataSource, new NullPointerException(), true ); } } ... }; ... mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor); } private void onNewResultInternal ( String id, DataSource<T> dataSource, @Nullable T image, float progress, boolean isFinished, boolean wasImmediate, boolean deliverTempResult) { Drawable drawable; try { drawable = createDrawable(image); } catch (Exception exception) { ... return ; } T previousImage = mFetchedImage; Drawable previousDrawable = mDrawable; mFetchedImage = image; mDrawable = drawable; try { if (isFinished) { mSettableDraweeHierarchy.setImage(drawable, 1f , wasImmediate); getControllerListener().onFinalImageSet(id, getImageInfo(image), getAnimatable()); } else if (deliverTempResult) { ... } else { ... } } finally { ... releaseDrawable(previousDrawable); ... releaseImage(previousImage); } }
那DataSource
是什么呢? getDataSource()
最终会调用到:
ImagePipeline.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public DataSource<CloseableReference<CloseableImage>> fetchDecodedImage(ImageRequest imageRequest,...) { Producer<CloseableReference<CloseableImage>> producerSequence = mProducerSequenceFactory.getDecodedImageProducerSequence(imageRequest); return submitFetchRequest( producerSequence, imageRequest, lowestPermittedRequestLevelOnSubmit, callerContext, requestListener); } private <T> DataSource<CloseableReference<T>> submitFetchRequest(...) { ... return CloseableProducerToDataSourceAdapter.create(producerSequence, settableProducerContext, finalRequestListener); }
所以DraweeController
最终拿到的DataSource
是CloseableProducerToDataSourceAdapter
。这个类在构造的时候就会启动图片加载流程(它的构造方法会调用producer.produceResults(...)
,这个方法就是图片加载的起点,我们后面再看)。
DataSource小结
Fresco
中DataSource
的概念以及作用:在Fresco
中DraweeController
每发起一次图片加载就会创建一个DataSource
,这个DataSource
用来提供这次请求的数据(图片)。DataSource
只是一个接口,至于具体的加载流程Fresco
是通过ProducerSequence
来实现的。
C层更具体的ProducerSequence怎么实现下载、解码、缓存的下面将进一步说明:
ProducerSequence ImagePipeline获取图片时,会根据不同的请求(获取解码图片:fetchDecodedImage,或者获取未解码图片:fetchEncodedImage)生成不同的Producer Sequence
,其实就是一个Producer
链条,每个Producer
只负责整个链条中的一环,例如:NetworkFetchProducer负责下载图片,DecodeProducer负责解码图片等。
Producer
的作用:一个Producer
用来处理整个Fresco
图片处理流程中的一步,比如从网络获取图片、内存获取图片、解码图片等等 ,而Consumer
可以把它理解为监听者。
Producer
的处理结果可以通过Consumer
来告诉外界,比如是失败还是成功。
一个ProducerA
可以接收另一个ProducerB
作为参数,如果ProducerA
处理完毕后可以调用ProducerB
来继续处理。并传入Consumer
来观察ProducerB
的处理结果。比如Fresco
在加载图片时会先去内存缓存获取,如果内存缓存中没有那么就网络加载。这里涉及到两个Producer
分别是BitmapMemoryCacheProducer
和NetworkFetchProducer
,假设BitmapMemoryCacheProducer
为ProducerA
,NetworkFetchProducer
为ProducerB
。
见附录:完整ProducerSequence;
这张图描述了Fresco
在第一次网络图片时所经历的过程,从图中可以看出涉及到缓存的Producer
共有4个:BitmapMemroyCacheGetProducer
、BitmapMemoryCacheProducer
、EncodedMemoryCacheProducer
和DiskCacheWriteProducer
。Fresco
在加载图片时会按照图中绿色箭头 所示依次经过这四个缓存Producer
,一旦在某个Producer
得到图片请求结果,就会按照蓝色箭头 所示把结果依次回调回来。简单介绍一下这4个Producer
的功能:
BitmapMemroyCacheGetProducer
: 这个Producer
会去内存缓存中检查这次请求有没命中缓存,如果命中则将缓存的图片作为这次请求结果。
BitmapMemoryCacheProducer
: 这个Producer
会监听其后面的Producer
的Result
,并把Result(CloseableImage)
存入缓存。
EncodedMemoryCacheProducer
: 它也是一个内存缓存,不过它缓存的是未解码的图片,即图片原始字节。
DiskCacheWriteProducer
: 顾名思义,它负责把图片缓存到磁盘,它缓存的也是未解码的图片。获取图片时如果命中了磁盘缓存那么就返回缓存的结果。
这里仅以Bitmap内存缓存为例子,展开说明PorducerSequence中其中 磁盘缓存或内存缓存 CacheProducer节点的工作流程:
BitmapMemroyCacheGetProducer
派生自BitmapMemoryCacheProducer
,与BitmapMemoryCacheProducer
的不同就是只读不写 而已。 大致看一下BitmapMemoryCacheProducer
的缓存运作逻辑:
BitmapMemoryCacheProducer.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class BitmapMemoryCacheProducer implements Producer <CloseableReference <CloseableImage >> { private final MemoryCache<CacheKey, CloseableImage> mMemoryCache; @Override public void produceResults (Consumer<CloseableReference<CloseableImage>> consumer...) { CloseableReference<CloseableImage> cachedReference = mMemoryCache.get(cacheKey); if (cachedReference != null ) { consumer.onNewResult(cachedReference, BaseConsumer.simpleStatusForIsLast(isFinal)); return ; } ... Consumer<CloseableReference<CloseableImage>> wrappedConsumer = wrapConsumer(consumer..); mInputProducer.produceResults(wrappedConsumer, producerContext); } protected Consumer<CloseableReference<CloseableImage>> wrapConsumer(){ return new DelegatingConsumer<...>(consumer) { @Override public void onNewResultImpl (CloseableReference<CloseableImage> newResult...) { newCachedResult = mMemoryCache.cache(cacheKey, newResult); getConsumer().onNewResult((newCachedResult != null ) ? newCachedResult : newResult, status); } } } }
它的主要流程图如下(后面两个缓存的流程与它基本相同,因此对于缓存整体流程只画这一次 ):
BitmapMemoryCacheProducer工作流.png
图中红色箭头和字体是正常网络加载图片(第一次)的步骤
内存缓存 : MemoryCache MemoryCache
是一个接口,在这里它的对应实现是CountingMemoryCache
, 先来看一下这个类的构造函数:
CountingMemoryCache.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class CountingMemoryCache<K, V> implements MemoryCache<K, V>, MemoryTrimmable { //缓存的集合其实就是一个map,不过这个map使用 Lru 算法 final CountingLruMap<K, Entry<K, V>> mExclusiveEntries; final CountingLruMap<K, Entry<K, V>> mCachedEntries; public CountingMemoryCache(ValueDescriptor<V> valueDescriptor,CacheTrimStrategy cacheTrimStrategy,Supplier<MemoryCacheParams> memoryCacheParamsSupplier) { mValueDescriptor = valueDescriptor;// 用来估算当前缓存实体的大小 mExclusiveEntries = new CountingLruMap<>(wrapValueDescriptor(valueDescriptor)); // 主要存放没有被引用的对象,它的所有元素一定在 mCachedEntries 集合中存在 mCachedEntries = new CountingLruMap<>(wrapValueDescriptor(valueDescriptor)); // 主要缓存集合 mCacheTrimStrategy = cacheTrimStrategy; // trim缓存的策略 (其实就是指定了trim ratio) mMemoryCacheParams = mMemoryCacheParamsSupplier.get(); // 通过 ImagePipelineConfig 来配置的缓存参数 } ... }
通过构造函数可以知道CountingMemoryCache
一共含有两个缓存集合 :
mCachedEntries
: 它是用来存放所有缓存对象的集合
mExclusiveEntries
: 它是用来存放当前没有被引用的对象,在trim
缓存是,主要是trim
掉这个缓存集合的中的对象。
CountingMemoryCache
的缓存逻辑主要是围绕这两个集合展开的。接下来看一下它的cache
和get
的方法(这两个方法是缓存的核心方法)。
将图片保存到内存缓存 : CountingMemoryCache.cache() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public CloseableReference<V> cache(K key, CloseableReference<V> valueRef, EntryStateObserver<K> observer) { Entry<K, V> oldExclusive; CloseableReference<V> oldRefToClose = null; CloseableReference<V> clientRef = null; synchronized (this) { 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); // 构造一个缓存实体(Entry) mCachedEntries.put(key, newEntry); //缓存 clientRef = newClientReference(newEntry); } } CloseableReference.closeSafely(oldRefToClose); //可能会调用到 release 方法, ... return clientRef; }
上面代码我做了比较详细的注释。简单的讲就是把这个对象放入到mCachedEntries
集合中,如果原来就已经缓存了这个对象,那么就要把它先从mCachedEntries
和mExclusiveEntries
集合中移除。
Fresco的默认内存缓存大小 上面canCacheNewValue()
是用来判断当前缓存是否已经达到了最大值。那Fresco
内存缓存的最大值是多少呢?这个值可以通过ImagePipelineConfig
来配置,如果没有配置的话默认配置是:DefaultBitmapMemoryCacheParamsSupplier
:
DefaultBitmapMemoryCacheParamsSupplier.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class DefaultBitmapMemoryCacheParamsSupplier implements Supplier<MemoryCacheParams> { ... private int getMaxCacheSize() { final int maxMemory = Math.min(mActivityManager.getMemoryClass() * ByteConstants.MB, Integer.MAX_VALUE); if (maxMemory < 32 * ByteConstants.MB) { return 4 * ByteConstants.MB; } else if (maxMemory < 64 * ByteConstants.MB) { return 6 * ByteConstants.MB; } else { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { return 8 * ByteConstants.MB; } else { return maxMemory / 4; } } } }
即Fresco
的默认缓存大小是根据当前应用的运行内存来决定的,对于应用运行内存达到64MB以上的手机(现在的手机普遍已经大于这个值了),Fresco
的默认缓存大小是maxMemory / 4
从内存缓存中获取图片 : CountingMemoryCache.get() 缓存获取的逻辑也很简单:
CountingMemoryCache.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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); } } maybeNotifyExclusiveEntryRemoval(oldExclusive); maybeUpdateCacheParams(); maybeEvictEntries(); return clientRef; }
即从mCachedEntries集合
中获取,如果mExclusiveEntries集合
中存在的话就移除。
trim策略 : CountingMemoryCache.getrimt() 当内存缓存达到峰值或系统内存不足时就需要对当前的内存缓存做trim
操作, trim
时是基于Lru
算法的,我们看一下它的具体逻辑:
1 2 3 4 5 6 7 8 9 10 11 public void trim(MemoryTrimType trimType) { ArrayList<Entry<K, V>> oldEntries; //根据当前的应用状态来确定trim ratio。 应用状态是指: 应用处于前台、后台等等 final double trimRatio = mCacheTrimStrategy.getTrimRatio(trimType); ... int targetCacheSize = (int) (mCachedEntries.getSizeInBytes() * (1 - trimRatio)); // trim到当前缓存的多少 int targetEvictionQueueSize = Math.max(0, targetCacheSize - getInUseSizeInBytes()); // 到底能trim多大 oldEntries = trimExclusivelyOwnedEntries(Integer.MAX_VALUE, targetEvictionQueueSize); //trim mExclusiveEntries集合 集合中的对象 makeOrphans(oldEntries); ... }
trim操作的主要步骤是:
根据当前应用的状态决定trim ratio
(应用状态是指应用处于前台、后台等等)。
根据trim ratio
来算出经过trim后缓存的大小targetCacheSize
根据mExclusiveEntries
集合的大小来决定到底能trim多少 (能trim的最大就是mExclusiveEntries.size)
对mExclusiveEntries
集合做trim
操作,即移除其中的元素。
即trim
时最大能trim
掉的大小是mExclusiveEntries
集合的大小。所以如果当前应用存在内存泄漏,导致mExclusiveEntries
中的元素很少,那么trim
操作几乎是没有效果的。
编码内存缓存 : EncodedMemoryCacheProducer 这个缓存Producer
的工作逻辑和BitmapMemoryCacheProducer
相同,不同的是它缓存的对象:
1 2 3 4 5 public class EncodedMemoryCacheProducer implements Producer<EncodedImage> { private final MemoryCache<CacheKey, PooledByteBuffer> mMemoryCache; ... }
即它缓存的是PooledByteBuffer
, 它是什么东西呢? 它牵扯到Fresco编码图片
的内存管理,这些内容我会单开一篇文章来讲一下。这里就先不说了。PooledByteBuffer
你可以简单的把它当成一个字节数组。
磁盘缓存 : DiskCacheWriteProducer 它是Fresco
图片磁盘缓存的逻辑管理者,整个缓存逻辑和BitmapMemoryCacheProducer
差不多:
1 2 3 4 5 6 7 8 9 10 11 12 public class DiskCacheWriteProducer implements Producer<EncodedImage> { private final BufferedDiskCache mDefaultBufferedDiskCache; / ... private static class DiskCacheWriteConsumer extends DelegatingConsumer<EncodedImage, EncodedImage> { @Override public void onNewResultImpl(EncodedImage newResult, @Status int status) { ... mDefaultBufferedDiskCache.put(cacheKey, newResult); } } }
接下来我们主要看一下它的磁盘存储逻辑(怎么存), 对于存储逻辑是由BufferedDiskCache
来负责的:
BufferedDiskCache 先来看一下类的组成结构:
1 2 3 4 5 public class BufferedDiskCache { private final FileCache mFileCache; // 文件存储的实现 private final Executor mWriteExecutor; //存储文件时的线程 private final StagingArea mStagingArea; }
FileCache : 将EncodeImage
保存到磁盘存储实现。
Executor : 指定文件保存操作所运行的线程。
StagingArea: 类似于git中的stage概念,它是一个map,用于保存当前正在进行磁盘缓存操作。
将图片保存至磁盘 : BufferedDiskCache.put() 这个方法主要负责往磁盘缓存一张图片:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public void put(final CacheKey key, EncodedImage encodedImage) { .. mStagingArea.put(key, encodedImage); //把这次缓存操作放到暂存区 ... final EncodedImage finalEncodedImage = EncodedImage.cloneOrNull(encodedImage); mWriteExecutor.execute( //开启写入线程 new Runnable() { @Override public void run() { try { writeToDiskCache(key, finalEncodedImage); //写入到磁盘 } finally { mStagingArea.remove(key, finalEncodedImage); //从操作暂存区中移除这次操作 EncodedImage.closeSafely(finalEncodedImage); } } }); } ... }
writeToDiskCache()
主要调用mFileCache.insert()
来把图片保存到磁盘:
1 2 3 4 5 6 7 mFileCache.insert(key, new WriterCallback() { @Override public void write(OutputStream os) throws IOException { mPooledByteStreams.copy(encodedImage.getInputStream(), os); //实际上就是把encodeImage 写入到 os(OutputStream) 中 } } );
至于mFileCache.insert()
的具体实现涉及的源码较多,考虑文章篇幅的原因这里我不去具体跟了。简单的总结一下其实现步骤和一些关键点:
Step1 : 生成ResourceId 这个ResourceId
可以简单的理解为缓存文件的文件名,它的生成算法如下:
1 SecureHashUtil.makeSHA1HashBase64(key.getUriString().getBytes("UTF-8")); // key就是CacheKey
即SHA-1
+ Base64
。
Step2 : 创建临时文件,并把图片写入到临时文件中
创建临时文件
1 2 3 public File createTempFile(File parent) throws IOException { return File.createTempFile(resourceId + ".", TEMP_FILE_EXTENSION, parent); }
把图片写入到这个临时文件中 : DefaultDiskStorage.java
1 2 3 4 5 6 public void writeData(WriterCallback callback, Object debugInfo) throws IOException { FileOutputStream fileStream = new FileOutputStream(mTemporaryFile); ... CountingOutputStream countingStream = new CountingOutputStream(fileStream); callback.write(countingStream); countingStream.flush();
这里的callback(WriterCallback)
就是mFileCache.insert()
方法传入的那个callback -> { mPooledByteStreams.copy(encodedImage.getInputStream(), os); }
Step3 : 把临时文件重命名为resourceId Step4 : 设置好文件的最后修改时间 从磁盘中获取文件 : BufferedDiskCache.get() 读就是写的逆操作,这里不做具体分析了。
Attention Fresco Key MemoryCacheKey 解码内存缓存时由CountingMemoryCache类真正实现,内部维护CountingLruMap (内为LinkedHashMap),value为CountingMemoryCache$Entry (持有CloseableReference->Bitmap)。
key为(内存缓存CacheKey子类 BitmapMemoryCacheKey ):对于内存的缓存,fresco根据 Uri字符串、缩放尺寸、解码参数、PostProcessor等关键参数进行hashCode 生成缓存key来缓存bitmap
EncodeImageKey 未解码内存缓存也由CountingMemoryCache类真正实现,内部维护CountingLruMap (内为LinkedHashMap),value为
CountingMemoryCache$Entry (真正持有是MemoryPooledByteBuffer->NativeMemoryChunk)
Key为默认实现是SimpleCacheKey:参数只有图片Uri
DiskCacheKey: 生成固定的缓存图片目录,直接把图片Uri哈希1后base64加密作为文件名,get时就直接”判断路径+hash1base64 “是否存在
缓存大小
内存缓存一般是 四分之一 ActivityManager.getMemoryClass
未解码的图片缓存一般是 4MB
磁盘缓存一般是 40MB
生命周期获取 通过DraweeView的onAttachedToWindow()和onDetachedFromWindow()时attachController()和detachController(),controller持有hierarchy,所以也会释放掉hierarchy。
DecodeProducer - Gif Decode *解码会根据为解码的图片格式,选择不同的解码器(PNG/JPEG/GIF/WEBP)*,此处仅以Gif图decode为分析对象(网络拉取gif、解码、缓存)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public DataSource<CloseableReference<CloseableImage>> fetchDecodedImage( ImageRequest imageRequest, Object callerContext, ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit, @Nullable RequestListener requestListener, @Nullable String uiComponentId) { try { Producer<CloseableReference<CloseableImage>> producerSequence = mProducerSequenceFactory.getDecodedImageProducerSequence(imageRequest); return submitFetchRequest( producerSequence, imageRequest, lowestPermittedRequestLevelOnSubmit, callerContext, requestListener, uiComponentId); } catch (Exception exception) { return DataSources.immediateFailedDataSource(exception); } } mNetworkFetchSequence = newBitmapCacheGetToDecodeSequence(getCommonNetworkFetchToEncodedMemorySequence()); multiplex -> encoded cache -> disk cache -> (webp transcode) -> network fetch. bitmap cache get -> background thread hand-off -> multiplex -> bitmap cache -> decode -> multiplex -> encoded cache -> disk cache -> (webp transcode) -> network fetch.
每一层生产者负责各自的责任,这里具体分析负责decode的DecodeProducer 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 DecodeProducer decodeProducer = mProducerFactory.newDecodeProducer(inputProducer); public DecodeProducer newDecodeProducer (Producer<EncodedImage> inputProducer) { return new DecodeProducer( mByteArrayPool, mExecutorSupplier.forDecode(), mImageDecoder, mProgressiveJpegConfig, mDownsampleEnabled, mResizeAndRotateEnabledForNetwork, mDecodeCancellationEnabled, inputProducer, mMaxBitmapSize, mCloseableReferenceFactory); }
下面分析mImageDecoder创建来源:
imageDecoder的创建 简述:就是构造一个单例的匿名内部ImageDecoder类,其中方法decodeGif()具体通过AnimatedImageFactoryImpl的同名方法实现。
首先是ProducerFactory的构建时就调用getImageDecoder
创建ImageDecode单例并入参。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 ImagePipelineFactory.class private ProducerFactory getProducerFactory () { if (mProducerFactory == null ) { mProducerFactory = mConfig .getExperiments() .getProducerFactoryMethod() .createProducerFactory( mConfig.getContext(), mConfig.getPoolFactory().getSmallByteArrayPool(), getImageDecoder(), } return mProducerFactory; } private ImageDecoder getImageDecoder () { if (mImageDecoder == null ) { if (mConfig.getImageDecoder() != null ) { mImageDecoder = mConfig.getImageDecoder(); } else { final AnimatedFactory animatedFactory = getAnimatedFactory(); ImageDecoder gifDecoder = null ; ImageDecoder webPDecoder = null ; if (animatedFactory != null ) { gifDecoder = animatedFactory.getGifDecoder(mConfig.getBitmapConfig()); webPDecoder = animatedFactory.getWebPDecoder(mConfig.getBitmapConfig()); } return mImageDecoder; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 AnimatedFactoryV2Impl implements AnimatedFactory public ImageDecoder getGifDecoder (final Bitmap.Config bitmapConfig) { return new ImageDecoder() { @Override public CloseableImage decode ( EncodedImage encodedImage, int length, QualityInfo qualityInfo, ImageDecodeOptions options) { return getAnimatedImageFactory().decodeGif(encodedImage, options, bitmapConfig); } }; } private AnimatedImageFactory getAnimatedImageFactory () { if (mAnimatedImageFactory == null ) { mAnimatedImageFactory = buildAnimatedImageFactory(); } return mAnimatedImageFactory; } private AnimatedImageFactory buildAnimatedImageFactory () { AnimatedDrawableBackendProvider animatedDrawableBackendProvider = new AnimatedDrawableBackendProvider() { @Override public AnimatedDrawableBackend get (AnimatedImageResult imageResult, Rect bounds) { return new AnimatedDrawableBackendImpl( getAnimatedDrawableUtil(), imageResult, bounds, mDownscaleFrameToDrawableDimensions); } }; return new AnimatedImageFactoryImpl(animatedDrawableBackendProvider, mPlatformBitmapFactory); }
encodeImage的decode过程 简述:先将未解码图像中解析出gif的元数据,封装 gif元数据(帧数、间隔、循环次数等) 与 未解码图像字节数组为 AnimatedImage(GifImage)。
通过getCloseableImage()根据option配置将上述AnimatedImage(GifImage)传入进一步解析,
即将 AnimatedImage(GifImage)中的未解码字节数组 进一步解析, 将AnimatedImage(GifImage)、预览图(maybe null)、预览帧(maybe null)、所有帧列表(如果打开了options.decodeAllFrames,maybe null),封装成AnimatedImageResult。
最后 GifImage在AnimatedDrawable2.draw 渲染时再进一步decode出需要的帧(GifFrame)
至于提到的 未解码字节数组 解析成帧的过程,是通过GifImage类实例sGifAnimatedImageDecoder,通过Native解码,主要是借助giflib 库在Native层进行解码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 AnimatedImageFactoryImpl implements AnimatedImageFactory{ static { sGifAnimatedImageDecoder = loadIfPresent("com.facebook.animated.gif.GifImage" ); sWebpAnimatedImageDecoder = loadIfPresent("com.facebook.animated.webp.WebPImage" ); } public CloseableImage decodeGif ( final EncodedImage encodedImage, final ImageDecodeOptions options, final Bitmap.Config bitmapConfig) { if (sGifAnimatedImageDecoder == null ) { throw new UnsupportedOperationException( "To encode animated gif please add the dependency " + "to the animated-gif module" ); } final CloseableReference<PooledByteBuffer> bytesRef = encodedImage.getByteBufferRef(); Preconditions.checkNotNull(bytesRef); try { final PooledByteBuffer input = bytesRef.get(); AnimatedImage gifImage; if (input.getByteBuffer() != null ) { gifImage = sGifAnimatedImageDecoder.decode(input.getByteBuffer()); } else { gifImage = sGifAnimatedImageDecoder.decode(input.getNativePtr(), input.size()); } return getCloseableImage(options, gifImage, bitmapConfig); } finally { CloseableReference.closeSafely(bytesRef); } } }
Gif解码后帧的缓存(问题出在哪) 还记得上面submitRequest后需要走加载流程后的图像加载成功结果onNewResultInternal
吗,
1 2 3 4 5 6 7 8 9 10 11 AnimatedImageFactoryImpl.class private CloseableImage getCloseableImage ( ImageDecodeOptions options, AnimatedImage image, Bitmap.Config bitmapConfig) { AnimatedImageResult animatedImageResult = AnimatedImageResult.newBuilder(image) .setPreviewBitmap(previewBitmap) .setFrameForPreview(frameForPreview) .setDecodedFrames(decodedFrames) .build(); return new CloseableAnimatedImage(animatedImageResult); }
这个方法会将AnimatedImage(GifImage)进一步封装成AnimatedResult(构建者模式构建),然后返回一个new CloseableAnimatedImage(animatedImageResult)
此时CloseableAnimatedImage中就有了新建的animatedImageResult(未解码的图像与gif元数据),最后这个closeableAnimatedImage会被传递到AbstractDraweeController.class
中的
onNewResultInternal(String id, DataSource<T> dataSource, @Nullable T image, float progress, boolean isFinished, boolean wasImmediate, boolean deliverTempResult)
,
执行 drawable = createDrawable(image);
此方法具体实现在PipelineDraweeController(extends AbstractDraweeController)
中
1 2 3 4 5 6 7 8 9 10 protected Drawable createDrawable (CloseableReference<CloseableImage> image) { ... drawable = mDefaultDrawableFactory.createDrawable(closeableImage); if (drawable != null ) { return drawable; } ... } }
mDefaultDrawableFactory
是ExperimentalBitmapAnimationDrawableFactory( implements DrawableFactory)
的实例,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 public AnimatedDrawable2 createDrawable (CloseableImage image) { return new AnimatedDrawable2( createAnimationBackend(((CloseableAnimatedImage) image).getImageResult())); } private AnimationBackend createAnimationBackend (AnimatedImageResult animatedImageResult) { ... BitmapFrameCache bitmapFrameCache = createBitmapFrameCache(animatedImageResult); ... } private AnimatedFrameCache createAnimatedFrameCache ( final AnimatedImageResult animatedImageResult) { return new AnimatedFrameCache( new AnimationFrameCacheKey(animatedImageResult.hashCode()), mBackingCache); } public class AnimationFrameCacheKey implements CacheKey { private static final String URI_PREFIX = "anim://" ; private final String mAnimationUriString; private final boolean mDeepEquals; public AnimationFrameCacheKey (int imageId) { mAnimationUriString = URI_PREFIX + imageId; } @Override public boolean equals (@Nullable Object o) { if (!mDeepEquals) { return super .equals(o); } if (this == o) { return true ; } if (o == null || getClass() != o.getClass()) { return false ; } AnimationFrameCacheKey that = (AnimationFrameCacheKey) o; return mAnimationUriString.equals(that.mAnimationUriString); } @Override public int hashCode () { if (!mDeepEquals) { return super .hashCode(); } return mAnimationUriString.hashCode(); } } public class AnimatedFrameCache { @Nullable public CloseableReference<CloseableImage> cache ( int frameIndex, CloseableReference<CloseableImage> imageRef) { return mBackingCache.cache(keyFor(frameIndex), imageRef, mEntryStateObserver); } private FrameKey keyFor (int frameIndex) { return new FrameKey(mImageCacheKey, frameIndex); } static class FrameKey implements CacheKey { private final CacheKey mImageCacheKey; private final int mFrameIndex; public FrameKey (CacheKey imageCacheKey, int frameIndex) { mImageCacheKey = imageCacheKey; mFrameIndex = frameIndex; } public boolean equals (Object o) { if (o == this ) { return true ; } if (o instanceof FrameKey) { FrameKey that = (FrameKey) o; return this .mFrameIndex == that.mFrameIndex && this .mImageCacheKey.equals(that.mImageCacheKey); } return false ; } } } public synchronized void onFrameRendered ( int frameNumber, CloseableReference<Bitmap> bitmapReference, @BitmapAnimationBackend .FrameType int frameType) { Preconditions.checkNotNull(bitmapReference); removePreparedReference(frameNumber); CloseableReference<CloseableImage> closableReference = null ; try { closableReference = createImageReference(bitmapReference); if (closableReference != null ) { CloseableReference.closeSafely(mLastRenderedItem); mLastRenderedItem = mAnimatedFrameCache.cache(frameNumber, closableReference); } } finally { CloseableReference.closeSafely(closableReference); } }
mBackingCache内存缓存池 缓存gif每一帧时,key为FrameKey,FrameKey由 整个Gif图形的缓存key mImageCacheKey + 帧索引mFrameIndex) 组成。
而 mImageCacheKey 是AnimationFrameCacheKey的实例,其中 核心变量为mAnimationUriString,是由
“anim://“ + animatedImageResult.hashCode组成的字符串,并保存布尔值mDeepEquals是否进行内容判断。
(animatedImageResult中保存了AnimatedImage(GifImage)、预览图(maybe null)、预览帧(maybe null)、所有帧列表(如果打开了options.decodeAllFrames,maybe null),是由 未解码gif图形缓存encodeImage 初步解析元数据后 的封装)
如果mDeepEquals是true的话,判断缓存是否存在
是依据FrameKey.mImageCacheKey.mAnimationUriString 内容是否相同 与 FrameKey.mFrameIndex是否相等。
如果mDeepEquals是false的话,判断缓存是否存在
是依据FrameKey.mImageCacheKey.mAnimationUriString 对象是否相同 与 FrameKey.mFrameIndex是否相等
由于原代码中由于mDeepEquals=false,且mAnimationUriString每次都是new出来的,所以必然不会相等,也就是帧内存缓存永远无法命中。
那么第一步就是mDeepEquals置为true,
但是由于mAnimationUriString = “anim://“ + animatedImageResult.hashCode
又:animatedImageResult是每次解析encodeImage都会重新构建的对象,也就是:
从 未解码图像缓存 中,同张图片 每次解析 都会得到不同的animatedImageResult,mAnimationUriString又是每一次解析都是不同的。
所以帧内存缓存又无法命中。
所以第二步需要 一个encodeImage对应的唯一 值替代 animatedImageResult.hashCode 。
考虑可以选择两个:
一个是 encodeImage的hash值, 未解码图片内存缓存是正确的,每次应用完整周期 都会有一个唯一的encodeImage 对应图像,decodeGif时encodeImage中存储未解码图像字节数组CloseableReference<PooledByteBuffer> mPooledByteBufferRef;
的getValueHash
方法。
1 2 3 4 5 6 CloseableReference.class Method used for tracking Closeables pointed by CloseableReference. Use only for debugging and logging. public int getValueHash () { return isValid() ? System.identityHashCode(mSharedReference.get()) : 0 ; }
一个是 根据url生成的cacheKey,设想是直接用图像url生成的磁盘缓存key,但是传递链太长了
我暂时选择的是getValueHash
像dokit跟fresco都在key的生成和使用中出过一些bug,以下是我的pullRequest
dokit对fresco框架下的大图检测功能导致图片闪烁(源于自定义的postProcessor每次都重新都新建了 内存缓存管理类CacheKey)
https://github.com/didi/DoraemonKit/pull/714/commits/4dbc58b84cfe1286c666d0cab5031092df52d81e
fresco官方中gif图缓存失效,导致重复加载同一张gif图时内存持续增长
(源于一个EndcodeImage在进一步解析成文件字节数组包裹的对象AnimatedResult时,每次都是新建的AnimatedResult对象,又用这个对象的hashcode+frameIndex作为解析成帧内存缓存时的key信息,所以就导致了帧内存缓存失效)
Gif cache doesn’t seem to be working. · Issue #2605 · facebook/fresco (github.com)
change imageId in AnimatedImageResult from the class hash to decodeHa… by WhileCrow · Pull Request #2612 · facebook/fresco (github.com)
附录:完整ProducerSequence 整个Producer Sequence
,如下所示:
NetworkFetchProducer : 负责从网络下载图片数据,内部持有NetworkFetcher,负责使用不同的Http框架去实现下载逻辑,例如:HttpUrlConnectionNetworkFetcher、OkHttpNetworkFetcher、VolleyNetworkFetcher等。
WebpTranscodeProducer : 因为不是所有Android平台都支持WebP
,具体可以参考WebpTranscodeProducer.shouldTranscode
方法,所以对于不支持WebP的平台,需要转换成jpg/png。其中无损或者带透明度的WebP(DefaultImageFormats.WEBP_LOSSLESS和DefaultImageFormats.WEBP_EXTENDED_WITH_ALPHA),需要转换成PNG,具体方法是先把WebP解码成RGBA,然后再把RGBA编码成PNG;简单或者扩展的WebP(DefaultImageFormats.WEBP_SIMPLE和DefaultImageFormats.WEBP_EXTENDED),需要转换成JPEG。具体方法是先把WebP解码成RGB,然后再把RGB编码成JPEG。
PartialDiskCacheProducer : 解下来的三个是磁盘缓存EncodedImage相关
DiskCacheWriteProducer
DiskCacheReadProducer
EncodedMemoryCacheProducer : 未解码数据的内存缓存
EncodedCacheKeyMultiplexProducer
AddImageTransformMetaDataProducer
ResizeAndRotateProducer : 负责采样和图片旋转
DecodeProducer : 上述的Producer都是基于EncodedImage,DecodeProducer会把EncodedImage解码成CloseableReference
BitmapMemoryCacheProducer : 接下来的两个是内存Bitmap缓存相关
BitmapMemoryCacheKeyMultiplexProducer
ThreadHandoffProducer : 负责切换线程
BitmapMemoryCacheGetProducer
PostprocessorProducer
PostprocessedBitmapMemoryCacheProducer
BitmapPrepareProducer
从下往上,依次持有引用;从上往下,依次返回数据
Other Fresco网络大图检测 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 private fun createDraweeViewInsnList () : InsnList { return with(InsnList()) { add(VarInsnNode(ALOAD, 0 )) add(VarInsnNode(ALOAD, 1 )) add(MethodInsnNode(INVOKESTATIC, "com/didichuxing/doraemonkit/aop/bigimg/fresco/MyFrescoHook" , "frescoRealHook" , "(Landroid/view/View;Lcom/facebook/drawee/interfaces/DraweeController;)V" , false )) this } } private fun createFrescoInsnList () : InsnList { return with(InsnList()) { add(VarInsnNode(ALOAD, 1 )) add(VarInsnNode(ALOAD, 1 )) add(MethodInsnNode(INVOKEVIRTUAL, "com/facebook/imagepipeline/request/ImageRequestBuilder" , "getSourceUri" , "()Landroid/net/Uri;" , false )) add(VarInsnNode(ALOAD, 1 )) add(MethodInsnNode(INVOKEVIRTUAL, "com/facebook/imagepipeline/request/ImageRequestBuilder" , "getPostprocessor" , "()Lcom/facebook/imagepipeline/request/Postprocessor;" , false )) add(MethodInsnNode(INVOKESTATIC, "com/didichuxing/doraemonkit/aop/bigimg/fresco/FrescoHook" , "proxy" , "(Landroid/net/Uri;Lcom/facebook/imagepipeline/request/Postprocessor;)Lcom/facebook/imagepipeline/request/Postprocessor;" , false )) add(MethodInsnNode(INVOKEVIRTUAL, "com/facebook/imagepipeline/request/ImageRequestBuilder" , "setPostprocessor" , "(Lcom/facebook/imagepipeline/request/Postprocessor;)Lcom/facebook/imagepipeline/request/ImageRequestBuilder;" , false )) this } }
参考
Fresco架构设计赏析 | susion
Fresco缓存架构分析 | susion
Fresco缓存架构分析 | susion
Fresco图片显示原理浅析 | susion
Fresco使用的扩展 | susion
GIF面面观 - 掘金 (juejin.cn)