Fresco 源码分析 —— 整体架构
Fresco 是我们项目中图片加载专用框架。虽然我不是负责 Fresco 框架,但是由本人负责组里的图片加载浏览等工作,因此了解 Fresco 的源码有助于我今后的工作,也可以学习 Fresco 的源码设计精髓。
由于 Fresco 源码比较多,仅凭一篇文章是无法将其说清楚的,因此会当做一个系列,详细介绍 Fresco 源码。本系列文章也会参考网上关于 Fresco 源码解析的文章,尽可能准确的去描述 Fresco 的实现原理,如有错误之处欢迎指出,欢迎交流学习。
Fresco 是一个强大的图片加载组件。使用它之后,你不需要再去关心图片的加载和显示这些繁琐的事情! 支持 Android 2.3 及以后的版本。如果需要了解 Fresco 的使用可以访问 Fresco 使用文档 。
Fresco是一个功能完善的图片加载框架,在Android开发中有着广泛的应用,那么它作为一个图片加载框架,有哪些特色让它备受推崇呢?
完善的内存管理功能,减少图片对内存的占用,即便在低端机器上也有着不错的表现。
自定义图片加载的过程,可以先显示低清晰度图片或者缩略图,加载完成后再显示高清图,可以在加载的时候缩放和旋转图片。
自定义图片绘制的过程,可以自定义谷中焦点、圆角图、占位图、overlay、进图条。
渐进式显示图片。
支持Gif。
支持Webp。
- ......
Fresco 的组成结构还是比较清晰的,大致如下图所示:

其实这两张图来自不同的文章,但是我觉得两者的分层实际上基本是一样的。只是一个比较概括,一个比价具体,将两者摆在一起,更有助于大家去理解其实现细节。当然除了 UI 和加载显示部分外,还有 Gif,动态图片等内容,以及对应图片解码编码逻辑等。这部分不打算去讲解,因为这部分虽然也是源码很重要的一部分,但是这部分需要相关专业知识才好说明白,此外且涉及到 C++ 代码。
下面结合代码分别解释一下上面各模块的作用以及大概的工作原理。
DraweeView
它继承自 ImageView,是 Fresco 加载图片各个阶段过程中图片显示的载体,比如在加载图片过程中它显示的是占位图、在加载成功时切换为目标图片。不过后续官方可能不再让这个类继承 ImageView,所以该类并不支持 ImageView 的 setImageXxx, setScaleType 以及其他类似的方法。目前 DraweeView 与 ImageView 唯一的交集是:它利用 ImageView 来显示 Drawable :
//DraweeView.setController()
public void setController(@Nullable DraweeController draweeController) {
mDraweeHolder.setController(draweeController);
super.setImageDrawable(mDraweeHolder.getTopLevelDrawable()); //super 就是 ImageView
} //DraweeHolder.getTopLevelDrawable()
public @Nullable Drawable getTopLevelDrawable() {
return mHierarchy == null ? null : mHierarchy.getTopLevelDrawable(); // mHierarchy 是 DraweeHierachy,
}
DraweeView.setController() 会在 Fresco 加载图片时会调用。其实在这里可以看出 Fresco 的图片显示原理是 : 利用 ImageView 显示DraweeHierachy 的 TopLevelDrawable。上面这段代码引出了 UI 层中另外两个关键类: DraweeHolder 和 DraweeHierachy。
DraweeHierachy
可以说它是 Fresco 图片显示的实现者。它的输出是 Drawable,这个 Drawable 会被 DraweeView 拿来显示(上面已经说了)。它内部有多个 Drawable,当前显示在 DraweeView 的 Drawable 叫做 TopLevelDrawable。在不同的图片加载阶段,TopLevelDrawable 是不同的(比如加载过程中是 placeholder,加载完成是目标图片)。具体的 Drawable 切换逻辑是由它来具体实现的。
它是由 DraweeController 直接持有的,因此对于不同图片显示的切换操作具体是由 DraweeController 来直接操作的。
DraweeHolder
可以把它理解为 DraweeView、DraweeHierachy 和 DraweeController 这 3 个类之间的粘合剂, DraweeView 并不直接和 DraweeController 和 DraweeHierachy 直接接触,所有的操作都是通过它传过去。这样,后续将 DraweeView 的父类改为 View,也不会影响到其他类。DraweeView 作为 View 可以感知点击和生命周期,通过 DraweeHolder 来控制其他两个类的操作。
想想如果是你,你会抽出 DraweeHolder 这样一个类吗?实际上,这里对我们平时开发也是有所借鉴,严格控制每一个类之间的关系,可以引入一些一些中间类,让类与类之间的关系耦合度降低,方便日后迭代。
具体引用关系如下图:

它的主要功能是: 接收 DraweeView 的图片加载请求,控制 ProducerSequence 发起图片加载和处理流程,监听 ProducerSequence 加载过程中的事件(失败、完成等),并更新最新的 Drawable 到 DraweeHierachy。
DraweeController 的构造逻辑
在 Fresco 中 DraweeController 是通过 PipelineDraweeControllerBuilderSupplier 获取的。Fresco在初始化时会调用下面的代码:
// Fresco.java
private static void initializeDrawee(Context context, @Nullable DraweeConfig draweeConfig) {
sDraweeControllerBuilderSupplier = new PipelineDraweeControllerBuilderSupplier(context, draweeConfig);
SimpleDraweeView.initialize(sDraweeControllerBuilderSupplier);
}
sDraweeControllerBuilderSupplier 是静态变量,也就是说其在只会初始一次。所有的 DraweeController 都是通过调用 sDraweecontrollerbuildersupplier.get() 得到的。
private void init(Context context, @Nullable AttributeSet attrs) {
try {
if (FrescoSystrace.isTracing()) {
FrescoSystrace.beginSection("SimpleDraweeView#init");
}
if (isInEditMode()) {
getTopLevelDrawable().setVisible(true, false);
getTopLevelDrawable().invalidateSelf();
} else {
Preconditions.checkNotNull(
sDraweecontrollerbuildersupplier, "SimpleDraweeView was not initialized!");
mControllerBuilder = sDraweecontrollerbuildersupplier.get(); // 调用一次就会创建一个新的实例
}
// ...... 省略其他代码
}
Fresco 每次图片加载都会对应到一个 DraweeController,一个DraweeView的多次图片加载可以复用同一个DraweeController:
SimpleDraweeView.java
public void setImageURI(Uri uri, @Nullable Object callerContext) {
DraweeController controller =
mControllerBuilder
.setCallerContext(callerContext)
.setUri(uri) //设置新的图片加载路径
.setOldController(getController()) //复用 controller
.build();
setController(controller);
}
所以一般情况下 : 一个 DraweeView 对应一个 DraweeController。
通过 DataSource 发起图片加载
在前面已经说了 DraweeController 是直接持有 DraweeHierachy,所以它观察到 ProducerSequence 的数据变化是可以很容易更新到 DraweeHierachy(具体代码先不展示了)。那它是如何控制 ProducerSequence 来加载图片的呢?其实 DraweeController 并不会直接和 ProducerSequence 发生关联。对于图片的加载,它直接接触的是 DataSource,由 DataSource 进而来控制 ProducerSequence 发起图片加载和处理流程。下面就跟随源码来看一下 DraweeController 是如果通过 DataSource 来控制 ProducerSequence 发起图片加载和处理流程的。
// AbstractDraweeController.java
protected void submitRequest() {
mDataSource = getDataSource();
final DataSubscriber<T> dataSubscriber = new BaseDataSubscriber<T>() { //可以简单的把它理解为一个监听者
@Override
public void onNewResultImpl(DataSource<T> dataSource) { //图片加载成功
...
}
...
};
...
mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor); //mUiThreadImmediateExecutor是指 dataSubscriber 回调方法运行的线程,这里是主线程
}
那 DataSource 是什么呢? getDataSource()最终会调用到:
// PipelineDraweeControllerBuilder
protected DataSource<CloseableReference<CloseableImage>> getDataSourceForRequest(
DraweeController controller,
String controllerId,
ImageRequest imageRequest,
Object callerContext,
AbstractDraweeControllerBuilder.CacheLevel cacheLevel) {
return mImagePipeline.fetchDecodedImage(
imageRequest,
callerContext,
convertCacheLevelToRequestLevel(cacheLevel),
getRequestListener(controller),
controllerId);
}
// CloseableProducerToDataSourceAdapter<T>
public static <T> DataSource<CloseableReference<T>> create(
Producer<CloseableReference<T>> producer,
SettableProducerContext settableProducerContext,
RequestListener2 listener) { CloseableProducerToDataSourceAdapter<T> result =
new CloseableProducerToDataSourceAdapter<T>(producer, settableProducerContext, listener);return result;
}
所以 DraweeController 最终拿到的 DataSource 是 CloseableProducerToDataSourceAdapter。这个类在构造的时候就会启动图片加载流程(它的构造方法会调用producer.produceResults(...),这个方法就是图片加载的起点,我们后面再看)。
这里我们总结一下 Fresco 中 DataSource 的概念以及作用: 在 Fresco 中 DraweeController 每发起一次图片加载就会创建一个 DataSource,这个 DataSource 用来提供这次请求的数据(图片)。DataSource 只是一个接口,至于具体的加载流程 Fresco 是通过 ProducerSequence 来实现的。
Fresco图片加载前的逻辑
了解了上面的知识后,我们过一遍图片加载的源码(从 UI 到 DraweeController),来理一下目前所了解的各个模块之间的联系。我们在使用 Fresco 加载图片时一般是使用这个API: SimpleDraweeView.setImageURI(imageLink),这个方法最终会调用到:
// SimpleDraweeView.java
public void setImageURI(Uri uri, @Nullable Object callerContext) {
DraweeController controller = mControllerBuilder
.setCallerContext(callerContext)
.setUri(uri)
.setOldController(getController())
.build(); //这里会复用 controller
setController(controller);
} public void setController(@Nullable DraweeController draweeController) {
mDraweeHolder.setController(draweeController);
super.setImageDrawable(mDraweeHolder.getTopLevelDrawable());
}
即每次加载都会使用 DraweeControllerBuilder 来 build 一个 DraweeController。其实这个 DraweeController 默认是复用的,这里的复用针对的是同一个 SimpleDraweeView
。然后会把 DraweeController 设置给 DraweeHolder,并在加载开始默认是从 DraweeHolder 获取 TopLevelDrawable 并展示到 DraweeView。继续看一下 DraweeHolder 的逻辑:
// DraweeHolder.java
public @Nullable Drawable getTopLevelDrawable() {
return mHierarchy == null ? null : mHierarchy.getTopLevelDrawable();
}
/** Sets a new controller. */
public void setController(@Nullable DraweeController draweeController) {
boolean wasAttached = mIsControllerAttached;
if (wasAttached) {
detachController();
}
// Clear the old controller
if (isControllerValid()) {
mEventTracker.recordEvent(Event.ON_CLEAR_OLD_CONTROLLER);
mController.setHierarchy(null);
}
mController = draweeController;
// 注意这里是只有确定已经 attached 才会调用,也就是才回去加载图片
if (wasAttached) {
attachController();
}
}
在DraweeHolder.setController() 中把 DraweeHierachy 设置给 DraweeController,并重新 attachController(),attachController()主要调用了DraweeController.onAttach():
// AbstractDraweeController.java
public void onAttach() {
...
mIsAttached = true;
if (!mIsRequestSubmitted) {
submitRequest();
}
} protected void submitRequest() {
mDataSource = getDataSource();
final DataSubscriber<T> dataSubscriber = new BaseDataSubscriber<T>() { //可以简单的把它理解为一个监听者
@Override
public void onNewResultImpl(DataSource<T> dataSource) { //图片加载成功
...
}
...
};
...
mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor); //mUiThreadImmediateExecutor是指 dataSubscriber 回调方法运行的线程,这里是主线程
}
即通过submitRequest()提交了一个请求,这个方法我们前面已经看过了,它所做的主要事情就是,构造了一个DataSource。这个 DataSource 我们经过追踪,它的实例实际上是CloseableProducerToDataSourceAdapter。CloseableProducerToDataSourceAdapter 在构造时就会调用 producer.produceResults(...),进而发起整个图片加载流程。
用下面这张图总结从SimpleDraweeView->DraweeController的图片加载逻辑:

到这里我们梳理完了 Fresco 在真正发起图片加载前所走的逻辑,那么 Fresco 的图片加载流程是如何控制的呢?到底经历了哪些步骤呢?
Producer
Fresco中有关图片的内存缓存、解码、编码、磁盘缓存、网络请求都是在这一层实现的,而所有的实现的基本单元是 Producer,所以我们先来理解一下 Producer:
看一下它的定义:
/**
* <p> Execution of image request consists of multiple different tasks such as network fetch,
* disk caching, memory caching, decoding, applying transformations etc. Producer<T> represents
* single task whose result is an instance of T. Breaking entire request into sequence of
* Producers allows us to construct different requests while reusing the same blocks.
*/
public interface Producer<T> { /**
* Start producing results for given context. Provided consumer is notified whenever progress is made (new value is ready or error occurs).
*/
void produceResults(Consumer<T> consumer, ProducerContext context);
}
结合注释我们可以这样定义 Producer 的作用:一个 Producer 用来处理整个 Fresco 图片处理流程中的一步,比如从网络获取图片、内存获取图片、解码图片等等。而对于 Consumer 可以把它理解为监听者,看一下它的定义:
public interface Consumer<T> {
/**
* Called by a producer whenever new data is produced. This method should not throw an exception.
*
* <p>In case when result is closeable resource producer will close it after onNewResult returns.
* Consumer needs to make copy of it if the resource must be accessed after that. Fortunately,
* with CloseableReferences, that should not impose too much overhead.
*
* @param newResult
* @param status bitwise values describing the returned result
* @see Status for status flags
*/
void onNewResult(T newResult, @Status int status);
/**
* Called by a producer whenever it terminates further work due to Throwable being thrown. This
* method should not throw an exception.
*
* @param t
*/
void onFailure(Throwable t);
/** Called by a producer whenever it is cancelled and won't produce any more results */
void onCancellation();
/**
* Called when the progress updates.
*
* @param progress in range [0, 1]
*/
void onProgressUpdate(float progress);
}
Producer 的处理结果可以通过 Consumer 来告诉外界,比如是失败还是成功。
Producer 的组合
一个 ProducerA 可以接收另一个 ProducerB 作为参数,如果 ProducerA 处理完毕后可以调用 ProducerB 来继续处理。并传入 Consumer 来观察 ProducerB 的处理结果。比如Fresco 在加载图片时会先去内存缓存获取,如果内存缓存中没有那么就网络加载。这里涉及到两个 Producer 分别是 BitmapMemoryCacheProducer 和 NetworkFetchProducer,假设BitmapMemoryCacheProducer 为 ProducerA,NetworkFetchProducer 为 ProducerB。我们用伪代码看一下他们的逻辑:
// BitmapMemoryCacheProducer.java
public class BitmapMemoryCacheProducer implements Producer<CloseableReference<CloseableImage>> {
private final Producer<CloseableReference<CloseableImage>> mInputProducer;
// 我们假设 inputProducer 在这里为NetworkFetchProducer
public BitmapMemoryCacheProducer(...,Producer<CloseableReference<CloseableImage>> inputProducer) {
...
mInputProducer = inputProducer;
}
@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..); //包了一层Consumer,即mInputProducer产生结果时,它自己可以观察到
mInputProducer.produceResults(wrappedConsumer, producerContext); //网络加载
}
}
// NetworkFetchProducer.java
public class NetworkFetchProducer implements Producer<EncodedImage> {
// 它并没有 inputProducer, 对于 Fresco 的图片加载来说如果网络都获取失败,那么就是图片加载失败了
@Override
public void produceResults(final Consumer<CloseableReference<CloseableImage>> consumer,..) {
// 网路获取
// ...
if(获取到网络图片){
notifyConsumer(...); //把结果通知给consumer,即观察者
}
...
}
}
代码可能不是很好理解,可以结合下面这张图来理解这个关系:

Fresco 可以通过组装多个不同的 Producer 来灵活的定义不同的图片处理流程的,多个 Producer 组装在一块称为 ProducerSequence (Fresco 中并没有这个类哦)。一个ProducerSequence 一般定义一种图片处理流程,比如网络加载图片的 ProducerSequence 叫做 NetworkFetchSequence,它包含多个不同类型的 Producer。
网络图片加载的处理流程
在 Fresco 中不同的图片请求会有不同的 ProducerSequence 来处理,比如网络图片请求:
// ProducerSequenceFactory.java
private Producer<CloseableReference<CloseableImage>> getBasicDecodedImageSequence(ImageRequest imageRequest) {
switch (imageRequest.getSourceUriType()) {
case SOURCE_TYPE_NETWORK: return getNetworkFetchSequence();
...
}
所以对于网络图片请求会调用 getNetworkFetchSequence:
/**
* swallow result if prefetch -> bitmap cache get -> background thread hand-off -> multiplex ->
* bitmap cache -> decode -> multiplex -> encoded cache -> disk cache -> (webp transcode) ->
* network fetch.
*/
private synchronized Producer<CloseableReference<CloseableImage>> getNetworkFetchSequence() {
...
mNetworkFetchSequence = new BitmapCacheGetToDecodeSequence(getCommonNetworkFetchToEncodedMemorySequence());
...
return mNetworkFetchSequence;
}
getNetworkFetchSequence 会经过重重调用来组合多个 Producer。这里我就不追代码逻辑了,直接用下面这张图来描述 Fresco 网络加载图片的处理流程:

可以看到 Fresco 的整个图片加载过程还是十分复杂的。并且上图我只是罗列一些关键的 Producer,其实还有一些我没有画出来。
总结
为了辅助理解,再提供一张总结的流程图,将上面整个过程都放在里面了。后续的系列文章会详细介绍 UI 和图片加载过程,希望通过阅读其源码来详细了解内部的代码逻辑以及设计思路。

其实我们在阅读别人源码的时候,除了要知道具体的细节之外,也要注意别人的模块设计,借鉴其设计思想。然后想想如果是你在设计的时候,你会怎么划分模块,如何将不同的模块联系起来。
当模块划分后,里面的子模块又是如何划分的,它们之间协作关系如何保持。
参考文章
Android开源框架源码鉴赏:Fresco
Fresco架构设计赏析
Fresco 源码分析 —— 整体架构的更多相关文章
- 精尽 MyBatis 源码分析 - 整体架构
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- zepto源码分析·整体架构
代码数量 1.2.0版本代码量为1650行,去掉注释大概1500左右 代码模块 默认版本只包括核心模块,事件模块,ajax模块,form模块和ie模块,其它模块需要自行拓展加入,其中form模块只包含 ...
- jquery-2.0.3 源码分析 整体架构
关键 var jQuery = function( selector, context ) { return new jQuery.fn.init(); } jQuery.fn = jQuery.pr ...
- [转]Libev源码分析 -- 整体设计
Libev源码分析 -- 整体设计 libev是Marc Lehmann用C写的高性能事件循环库.通过libev,可以灵活地把各种事件组织管理起来,如:时钟.io.信号等.libev在业界内也是广受好 ...
- Fresco 源码分析(二) Fresco客户端与服务端交互(3) 前后台打通
4.2.1.2.4 PipelineDraweeControllerBuilder.obtainController()源码分析 续 上节中我们提到两个核心的步骤 obtainDataSourceSu ...
- Fresco 源码分析(二) Fresco客户端与服务端交互(1) 解决遗留的Q1问题
4.2 Fresco客户端与服务端的交互(一) 解决Q1问题 从这篇博客开始,我们开始讨论客户端与服务端是如何交互的,这个交互的入口,我们从Q1问题入手(博客按照这样的问题入手,是因为当时我也是从这里 ...
- Fresco 源码分析(一) DraweeView-DraweeHierarchy-DraweeController(MVC) DraweeHierachy+DraweeController的分析
4.1.5.2 模型层DraweeHierachy继承体系以及各个类的作用 DraweeHierachy (I) --| SettableDraweeHierarchy (I) ------| Gen ...
- MyBatis 源码篇-整体架构
MyBatis 的整体架构分为三层, 分别是基础支持层.核心处理层和接口层,如下图所示. 基础支持层 反射模块 该模块对 Java 原生的反射进行了良好的封装,提供了更加简洁易用的 API ,方便上层 ...
- Fresco 源码分析(三) Fresco服务端处理(1) ImagePipeline为何物
4.3 服务端的处理 备注: 因为是分析,而不是设计,所以很多知识我们类似于插叙的方式叙述,就是用到了哪个知识点,我们再提及相关的知识点,如果分析到了最后,我想想是不是应该将这个架构按照设计的方式,重 ...
随机推荐
- ATT&CK 实战 - 红日安全 vulnstack (一) 靶机渗透
关于部署:https://www.cnblogs.com/Cl0ud/p/13688649.html PS:好菜,后来发现内网主机还是PING不通VM1,索性三台主机全部配成NAT模式,按照WEB靶机 ...
- 乌云wooyun网站硬盘复活
AWD比赛防止没有网络,在移动硬盘里面准备一个乌云漏洞库. 之前也想过弄一个乌云的镜像网站,无奈学生机性能太低下了,部署到公网上服务器存储空间都不够,只能部署在本地硬盘了. 乌云镜像的开源地址:htt ...
- burp-requests插件安装使用
这段时间都没更博客,扫描器的更新也暂时停止了,因为回了学校之后需要准备实验室招新和几个比赛的事情,内疚两秒钟,赶快学习! burp里面的插件很多,但是不要被纷繁复杂的功能迷了双眼,还是那句话:适合自己 ...
- 公司只提供签名服务,不提供证书文件,如何打包Electron应用
需求 稍微正规点的公司,都要为自己开发的软件做代码签名,如下图所示 代码签名的主要目的是为了确保软件的来源(这个软件是由谁生产的)和软件的内容不被篡改 一个软件公司可能有很多团队,很多开发者,开发不同 ...
- 软工个人项目 ——wc.exe
1.GitHub项目地址 https://github.com/k8kiw/WordCount 2.PSP预计时间 PSP2.1 Personal Software Process Stages 预估 ...
- 深入解析ConcurrentHashMap:感受并发编程智慧
如果有一个整型变量count,多个线程并发让count自增1,你会怎么设计? 你知道如何让多个线程协作完成一件事件吗? 前言 很高兴遇见你~ ConcurrentHashMap是个老生常谈的集合类了, ...
- Grafana 备份恢复教程
原文链接:https://fuckcloudnative.io/posts/how-to-back-up-all-of-your-grafana-dashboards/ 目前我们 k8s 集群的 Gr ...
- 深度学习炼丹术 —— Taoye不讲码德,又水文了,居然写感知器这么简单的内容
手撕机器学习系列文章就暂时更新到此吧,目前已经完成了支持向量机SVM.决策树.KNN.贝叶斯.线性回归.Logistic回归,其他算法还请允许Taoye在这里先赊个账,后期有机会有时间再给大家补上. ...
- 在IDEA中使用JDBC获取数据库连接时的报错及解决办法
在IDEA中使用JDBC获取数据库连接时,有时会报错Sat Dec 19 19:32:18 CST 2020 WARN: Establishing SSL connection without ser ...
- 测试提bug及出现漏测情况时的注意点
提bug注意(此为公司开发提出的建议): 开发如果改bug影响导致另一个问题,原bug没有问题,尽量重新提bug,不要直接激活,因为可能不是同一个问题导致的: 不要一个bug里提多个问题,因为不同 ...