梳理了Fresco的整体架构、网络请求流程、三级缓存策略。
第一节主要是源码追踪,可以直接跳过从第二节开始阅读。
加载框架代码
- 官网示例代码
官方的示例代码,创建一个DraweeView,调用setImageUri就可以触发加载。

- setImageURI
里面代码就是创建DraweeController。每次图片加载都会对应到一个DraweeController,它负责加载图片,以及将图片提供给DraweeView进行显示。

- setController
DraweeView中和DraweeHolder中setController代码分别如下,这里也出现了一个新的对象DraweeHierarchey,暂时忽略它。
将DraweeView和DraweeHierarchey看成同一个东西,他们负责将Controller加载成功后或者其他默认图显示出来也就是view。
DraweeHolder相当于两者之间的桥梁,将View和Controller结合起来。


- attachController
attachController()方法需要同时满足几个条件,设置了controller、控件可见、view.onAttach。

Controller的attach()代码在AbstractDraweeController中。调用了submitRequest()。在其中又调用DataSource开始加载。
scss
// AbstractDraweeController.java
public void onAttach() {
......
if (!mIsRequestSubmitted) {
submitRequest();
}
}
protected void submitRequest() {
......
mDataSource = getDataSource();
final DataSubscriber<T> dataSubscriber = ...
mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor);
}
- DataSource
追踪dataSource的来源,又找到了新东西ImagePipeline,先直接点进去看

到ImagePipeline里面,这个方法做了两件事,创建ProducerSequence,然后是submitFetchRequest()
ProducerSequence控制加载,Fresco的加载流程都是通过它完成
submitFetchRequest()方法一路看下去,会走AbstractProducerToDataSourceAdapter的构造方法,其中调用producer.produceResults()启动加载


加载框架流程
通过上一节代码的追踪,加载从DraweeView.setUri()开始,层层调用,经过controller层最终到了最底部的ProducerSequence开始了加载,加载成功后再层层回调到view层,具体流程:

这里要注意几个关键的部分:
- DraweeView和DraweeHierarchey,他们负责将图显示出来,DraweeHolder将它们和DraweeController链接起来,是两者之间的桥梁,将两者结合起来以及两者之间做部分解耦
- DataSource负责图片加载,直接由DraweeController控制,加载流程最终是ProducerSequence
- DraweeController参与图片加载所有的流程
这样已经是很熟悉的MVC结构了,如图:
暂时无法在飞书文档外展示此内容
网络请求流程
知道了整体的结构,再来梳理数据加载流程,也就是ProducerSequence。
- producer
先看producer的定义,一个只有一个方法的接口,context携带上下文信息,存储producer应该加载的图像的属性信息;consumer是消费者,通过它将加载状态以及加载结果回调出去。

再来看一个consumer的代理实现,本身作为Consumer,再通过构造方法持有另一个Consumer-A,通过对A的代理和增强,实现额外的功能和逻辑,属于装饰器的设计模式。

接着要来看一个Producer的实现,这里的代码来自ThrottlingProducer,去除了所有的处理逻辑,只留下了架构方面的代码。
跟上面类似,本身作为Producer-- A,再通过构造方法持有另一个Producer--B,在produceResults中作了一些逻辑处理之后,调用Producer--B的produceResults方法,传入的消费者consumer是代理了外部来源的consumer--c的xxxxConsumer--A。在xxxxConsumer--A的回调中,调用了consumer--c的回调之后再额外处理了一部分自身的业务逻辑。
设计上来说就是通过对Producer--B的持有,再回调consumer--c的基础上扩展了Producer-- A和Consumer--A的功能,属于代理模式。
java
public class XXXXProducer<T> implements Producer<T> {
private final Producer<T> mInputProducer;
public XXXXProducer( final Producer<T> inputProducer) {
...
mInputProducer = inputProducer;
}
@Override
public void produceResults(final Consumer<T> consumer, final ProducerContext producerContext) {
...
mInputProducer.produceResults(new XXXXConsumer(consumer), producerContext);
}
private class XXXXConsumer extends DelegatingConsumer<T, T> {
private XXXXConsumer(Consumer<T> consumer) {
super(consumer);
}
@Override
protected void onNewResultImpl(T newResult, @Status int status) {
getConsumer().onNewResult(newResult, status);
...
}
}
}
暂时无法在飞书文档外展示此内容
- producerSequence
在第一节结尾的时候,停在了producerSequence的创建方法,调用ProducerSequenceFactoryd的getDecodedImageProducerSequence(),然后调用了getBasicDecodedImageSequence(),这里根据Uri的不同类型创建不同的producerSequence。

以network为例,getNetworkFetchSequence()中,newBitmapCacheGetToDecodeSequence(getCommonNetworkFetchToEncodedMemorySequence())方法看起来,就是外层包装BitmapCash的一个producer,里面继续创建Seqence,这样一层一层包起来一个链式的producer。

源码流程很多,这里不再贴出,整理了一下最终的ProducerSequence,getNetworkFetchSequence()最终的层级结构:
-
BitmapMemoryCacheGetProducer,图片缓存读取
-
ThreadHandoffProducer,线程切换
-
BitmapMemoryCacheKeyMultiplexProducer,多路复用,相同请求进行合并
-
BitmapMemoryCacheProducer,图片缓存
-
ResizeAndRotateProducer,图片调整
-
AddImageTransformMetaDataProducer,元数据解码
-
EncodedCacheKeyMultiplexProducer,元数据多路复用
-
EncodedMemoryCacheProducer,元数据缓存
-
DiskCacheReadProducer,磁盘缓存读取
-
DiskCacheWriteProducer,磁盘缓存写入
-
WebpTranscodeProducer,webp转码
-
NetworkFetchProducer,网络请求
这里不得不感叹Fresco本身的强大,一次网络请求竟然需要这么多的流程,通过这里Prodcer的排列,可以看出Fresco的缓存框架:
-
总共具有三层缓存
-
第一层Bitmap缓存,Bitmap缓存存储Bitmap对象,这些Bitmap对象可以立即用来显示,在线程切换之前就读缓存,缓存在内存当中,在后台会被清掉,Bitmap相对于元数据会大很多,参考之前的Bitmap相关知识
-
第二层元数据缓存,元数据缓存存储原始压缩格式图片如png、jpg,这些缓存在使用时需要先解码成bitmap,使用会再次缓存到第一层缓存,缓存在内存中,在后台会被清掉
-
第三层元数据缓存,与第二层的缓存数据完全一致,使用时需要解码,使用会再次缓存到第一层和第二层缓存中,缓存在磁盘中,在后台不回被清除
-
总结
学习和梳理了Fresco的整体架构和加载流程整理完成,但明显可以看出,这在它整个的设计中知识很小的一部分,这里还可以挖掘的点特别多,比如图片解码、缓存管理、磁盘管理。但有了这些框架的知识,后面继续学习这些更细节的东西也方便了很多。
参考文章