fresco

Fresco(2.5.0)

以MVC的 Fresco架构入手,层层递进分析fresco的整体思路。

img

个人理解会将 图中的UI层进一步分为 DraweeView为View层(负责渲染),DraweeHierarchy为Model层(记录配置与数据Drawable[6]),DraweeController为Controller层(分离PorducerSequence加载缓存责任链条)

ps:MVC现在很多变种中都会有View层与Model层 交互,View层与Controller层 交互。但一般都不会有Model持有Controller。所以我会将fresco理解为MVC。

imgimg

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();
}

所以最终的显示的DrawablemHierarchy.getTopLevelDrawable()mHierarchy的实现是GenericDraweeHierarchymHierarchy.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); //RootDrawable 只是一个装饰类
}

FadeDrawable内部维护着一个Drawable数组,它可以由一个Drawable切换到另一个DrawableDrawable的切换过程中伴有着透明度改变的动画:

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);
}
}
}

img

主要有顶级图层,占位符图层,目标显示图层,重新加载图层,显示失败图层, 进度条图层,控制覆盖图层

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对象未解码图片的内存缓存图片的磁盘缓存网络拉取

img

DraweeController的构造

FrescoDraweeController是通过DraweeControllerBuilder来构造的。而DraweeControllerBuilderFresco中是以单例的形式存在的。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()) //复用 controller
.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(); //从mImagePipeline.getBitmapMemoryCache()中寻找内存(解码后)是否存在
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(), /* isFinished */ true);
}
}
...
};
...
mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor); //mUiThreadImmediateExecutor是指 dataSubscriber 回调方法运行的线程,这里是主线程
}

private void onNewResultInternal(
String id,
DataSource<T> dataSource,
@Nullable T image,
float progress,
boolean isFinished,
boolean wasImmediate,
boolean deliverTempResult) {

// create drawable
Drawable drawable;
try {
drawable = createDrawable(image);
} catch (Exception exception) {
...
return;
}
T previousImage = mFetchedImage;
Drawable previousDrawable = mDrawable;
mFetchedImage = image;
mDrawable = drawable;
try {
// set the new image
if (isFinished) {
//完成加载,设置Hierarchy(View层)图层,并由controller监听器回调切换图层
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,...) {
//获取加载图片的ProducerSequence
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最终拿到的DataSourceCloseableProducerToDataSourceAdapter。这个类在构造的时候就会启动图片加载流程(它的构造方法会调用producer.produceResults(...),这个方法就是图片加载的起点,我们后面再看)。

DataSource小结

FrescoDataSource的概念以及作用:FrescoDraweeController每发起一次图片加载就会创建一个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分别是BitmapMemoryCacheProducerNetworkFetchProducer,假设BitmapMemoryCacheProducerProducerANetworkFetchProducerProducerB

见附录:完整ProducerSequence;

img

这张图描述了Fresco在第一次网络图片时所经历的过程,从图中可以看出涉及到缓存的Producer共有4个:BitmapMemroyCacheGetProducerBitmapMemoryCacheProducerEncodedMemoryCacheProducerDiskCacheWriteProducerFresco在加载图片时会按照图中绿色箭头所示依次经过这四个缓存Producer,一旦在某个Producer得到图片请求结果,就会按照蓝色箭头所示把结果依次回调回来。简单介绍一下这4个Producer的功能:

  1. BitmapMemroyCacheGetProducer: 这个Producer会去内存缓存中检查这次请求有没命中缓存,如果命中则将缓存的图片作为这次请求结果。

  2. BitmapMemoryCacheProducer: 这个Producer会监听其后面的ProducerResult,并把Result(CloseableImage)存入缓存。

  3. EncodedMemoryCacheProducer: 它也是一个内存缓存,不过它缓存的是未解码的图片,即图片原始字节。

  4. 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...){

//1.先去缓存中获取
CloseableReference<CloseableImage> cachedReference = mMemoryCache.get(cacheKey);

//2.命中缓存直接返回请求结果
if (cachedReference != null) {
consumer.onNewResult(cachedReference, BaseConsumer.simpleStatusForIsLast(isFinal));
return;
}

...
//3.wrapConsumer来观察后续Producer的结果
Consumer<CloseableReference<CloseableImage>> wrappedConsumer = wrapConsumer(consumer..);

//4.让下一个Producer继续工作
mInputProducer.produceResults(wrappedConsumer, producerContext);
}

protected Consumer<CloseableReference<CloseableImage>> wrapConsumer(){
return new DelegatingConsumer<...>(consumer) {

@Override
public void onNewResultImpl(CloseableReference<CloseableImage> newResult...){
//5.缓存结果
newCachedResult = mMemoryCache.cache(cacheKey, newResult);

//6.通知前面的Producer图片请求结果
getConsumer().onNewResult((newCachedResult != null) ? newCachedResult : newResult, status);
}
}
}
}

它的主要流程图如下(后面两个缓存的流程与它基本相同,因此对于缓存整体流程只画这一次):

img

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的缓存逻辑主要是围绕这两个集合展开的。接下来看一下它的cacheget的方法(这两个方法是缓存的核心方法)。

将图片保存到内存缓存 : 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集合中,如果原来就已经缓存了这个对象,那么就要把它先从mCachedEntriesmExclusiveEntries集合中移除。

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操作的主要步骤是:

  1. 根据当前应用的状态决定trim ratio (应用状态是指应用处于前台、后台等等)。
  2. 根据trim ratio来算出经过trim后缓存的大小targetCacheSize
  3. 根据mExclusiveEntries集合的大小来决定到底能trim多少 (能trim的最大就是mExclusiveEntries.size)
  4. 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 “是否存在

缓存大小

  1. 内存缓存一般是 四分之一 ActivityManager.getMemoryClass
  2. 未解码的图片缓存一般是 4MB
  3. 磁盘缓存一般是 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
//ImagePipeline.java
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);
//封装到DataSorce中处理
return submitFetchRequest(
producerSequence,
imageRequest,
lowestPermittedRequestLevelOnSubmit,
callerContext,
requestListener,
uiComponentId);
} catch (Exception exception) {
return DataSources.immediateFailedDataSource(exception);
}
}

//mProducerSequenceFactory.getDecodedImageProducerSequence 调用的是getBasicDecodedImageSequence


//ProducerSequenceFactory.class
//ProducerSequenceFactory.getBasicDecodedImageSequence 方法中会根据imageRequest的uri的SCHEME(http/https、file等)选择合适的生产者序列工厂。此处以经典的https为例,返回的是:网络拉取序列单例
mNetworkFetchSequence =
newBitmapCacheGetToDecodeSequence(getCommonNetworkFetchToEncodedMemorySequence());


//getCommonNetworkFetchToEncodedMemorySequence负责:
multiplex -> encoded cache -> disk cache -> (webp transcode) -> network fetch.

//newBitmapCacheGetToDecodeSequence追加producer后:
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
//ProducerSequenceFactory.BitmapCacheGetToDecodeSequence()
DecodeProducer decodeProducer = mProducerFactory.newDecodeProducer(inputProducer);

//ProducerFactory.class
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 {

/*getAnimatedFactory()利用AnimatedFactoryProvider以反射方式实例化AnimatedFactoryV2Impl并返回*/
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

//AnimatedFactoryV2Impl.class
//AnimatedFactoryV2Impl.getGifDeocder()中直接返回:
//由AnimatedFactoryV2Impl.getAnimatedImageFactory().decodeGif()实现解码的匿名内部类ImageDecoder实例;其中getAnimatedIamgeFractory()返回AnimatedImageFactoryImpl单例。
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解析encodeImage成CloseableImage
AnimatedImageFactoryImpl implements AnimatedImageFactory{
static {
sGifAnimatedImageDecoder = loadIfPresent("com.facebook.animated.gif.GifImage");
sWebpAnimatedImageDecoder = loadIfPresent("com.facebook.animated.webp.WebPImage");
}


//将encodeImage(未解码的图像,包括未解码字节与元数据的native字节数组)解析成CloseableImage(解析元数据后的图像信息)
//GifImage就代表一个GIF,这里只会解析出GIF的元数据,不会真正解码GIF帧
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();

/*AnimateImage:Gif的表示,具体实现为GifImage,
该类的实例将在内存中保存未解码数据的副本和已解码的元数据(即解析出了图像的帧数、间隔、循环次数等元数据)。
ps:帧通过GifFrame按需解码。*/
AnimatedImage gifImage;
if (input.getByteBuffer() != null) {

/*sGifAnimatedImageDecoder其实也就是是GifImage*/
gifImage = sGifAnimatedImageDecoder.decode(input.getByteBuffer());
} else {
gifImage = sGifAnimatedImageDecoder.decode(input.getNativePtr(), input.size());
}

/*上面解析出来的AnimateImage信息,根据option配置,决定是否解析成静态图片,是否解析全部帧位图列表,是否设置预览帧等进一步封装成CloseableImage*/
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
//PipelineDraweeController
protected Drawable createDrawable(CloseableReference<CloseableImage> image) {
...
drawable = mDefaultDrawableFactory.createDrawable(closeableImage);
if (drawable != null) {
return drawable;
}
...
}
}

mDefaultDrawableFactoryExperimentalBitmapAnimationDrawableFactory( 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
//ExperimentalBitmapAnimationDrawableFactory.class
public AnimatedDrawable2 createDrawable(CloseableImage image) {
return new AnimatedDrawable2(
//((CloseableAnimatedImage) image).getImageResult())也就是上面AnimatedImageFactoryImpl.getCloseableImage返回的AnimatedResult对象。
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; //源码中为false


//imageId = animatedImageResult.hashCode()
public AnimationFrameCacheKey(int imageId) {
mAnimationUriString = URI_PREFIX + imageId;
//e.g: “anim://306123060"
}

//重写equals与hashCode方法以求 得到正确的比对结果


@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);
}


//每一帧的缓存Key。equals时比较类型、
static class FrameKey implements CacheKey {

//这里CacheKey 是 AnimationFrameCacheKey
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);

// Close up prepared references.
removePreparedReference(frameNumber);

// Create the new image reference and cache it.
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;
}
//identityHashCode ≈ hashCode()

一个是 根据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,如下所示:

  1. NetworkFetchProducer : 负责从网络下载图片数据,内部持有NetworkFetcher,负责使用不同的Http框架去实现下载逻辑,例如:HttpUrlConnectionNetworkFetcher、OkHttpNetworkFetcher、VolleyNetworkFetcher等。
  2. 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。
  3. PartialDiskCacheProducer : 解下来的三个是磁盘缓存EncodedImage相关
  4. DiskCacheWriteProducer
  5. DiskCacheReadProducer
  6. EncodedMemoryCacheProducer : 未解码数据的内存缓存
  7. EncodedCacheKeyMultiplexProducer
  8. AddImageTransformMetaDataProducer
  9. ResizeAndRotateProducer : 负责采样和图片旋转
  10. DecodeProducer : 上述的Producer都是基于EncodedImage,DecodeProducer会把EncodedImage解码成CloseableReference
  11. BitmapMemoryCacheProducer : 接下来的两个是内存Bitmap缓存相关
  12. BitmapMemoryCacheKeyMultiplexProducer
  13. ThreadHandoffProducer : 负责切换线程
  14. BitmapMemoryCacheGetProducer
  15. PostprocessorProducer
  16. PostprocessedBitmapMemoryCacheProducer
  17. 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
//插桩
/*
思路:draweeView在xml或手动setUri最终都需要为draweeView构建DraweeController,由此得到了统一的draweeView传入链接的入口
时机:构建完将该DraweeController设置为controller时
目的:获得draweeView的宽高(注册其布局回调完成)、draweeView所在页面及id、图片链接 */
private fun createDraweeViewInsnList(): InsnList {
return with(InsnList()) {
add(VarInsnNode(ALOAD, 0)) //由于DraweeView.setController方法内引用了全局变量,故方法字节码中会将DraweeView对象最为参数传入方法
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
}
}

//插桩
/* 思路:设置自定义postProcessor,postProcessor会回调图片加载完成时机
时机:在图片下载解析完成时
目的:获得内存图片大小与图片链接 */
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
}

}

//之后就是以图片链接为桥梁,将两个不同时机获取的信息整合,比对内存尺寸与view尺寸

参考

Fresco架构设计赏析 | susion

Fresco缓存架构分析 | susion

Fresco缓存架构分析 | susion

Fresco图片显示原理浅析 | susion

Fresco使用的扩展 | susion

GIF面面观 - 掘金 (juejin.cn)

Author

white crow

Posted on

2021-04-09

Updated on

2024-04-24

Licensed under