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 服务端的处理 备注: 因为是分析,而不是设计,所以很多知识我们类似于插叙的方式叙述,就是用到了哪个知识点,我们再提及相关的知识点,如果分析到了最后,我想想是不是应该将这个架构按照设计的方式,重 ...
随机推荐
- CNVD漏洞证书(2)
第二张CNVD的原创漏洞证书. 关于证书申请可以看我之前写的这篇博客: https://www.cnblogs.com/Cl0ud/p/12720413.html 继续加油
- python接口自动化测试框架(post提交添加变量)
1.python接口测试框架包含哪几部分 数据源-> GET/POST 发送请求->接收返回结果->断言测试结果->生成测试报告(html报告)->网页报告 2.pyth ...
- Nginx 转发时的一个坑,运维居然让我背锅!!
最近遇到一个 Nginx 转发的坑,一个请求转发到 Tomcat 时发现有几个 http header 始终获取不到,导致线上出现 bug,运维说不是他的问题,这个锅我背了. 新增的几个 header ...
- vue props默认值国际化报错
未做国际化处理 tabLabel: { type: Array, default: () => (["a", "b", "c"]) } ...
- SA-IS学习笔记
Q:SA-IS 是什么玩意? A:一种 \(O(n)\) 求后缀数组的高科技. Q:为什么会有 SA-IS 这种算法? A:因为它是 \(O(n)\) 的,比倍增 \(O(n\log n)\) 快. ...
- GYM100526I Interesting Integers
题目大意 定义一种 \(Gabonacci\) 数列: \[\begin{array}{c} G_1=a\\ G_2=b\\ G_i=G_{i-1}+G_{i-2} \end{array} \] 给定 ...
- 36个JS特效教程,学完即精通
6个JS特效教程,学完即精通 JavaScript特效教程,学完你就能写任何特效.本课程将JavaScript.BOM.DOM.jQuery和Ajax课程中的各种网页特效提取出了再进行汇总.内容涵 ...
- Docker的容器使用与连接-Window
启动容器 启动容器之前需要先拉取镜像,然后通过 run 命令启动容器,同一个镜像可以启动多个容器,只要执行多次 run 命令就行了.我们这边启动 centos 的镜像. PS D:\> dock ...
- c++ 解析yaml文件
一直用c++操作ini做配置文件,想换成yaml,在全球最大的同性交友网站github上搜索,看有没有开源的库,功夫不负有心人,找到了yaml-cpp,用他解析了一个yaml的例子非常好使,分享一下如 ...
- Springboot mini - Solon详解(七)- Solon Ioc 的注解对比Spring及JSR330
Springboot min -Solon 详解系列文章: Springboot mini - Solon详解(一)- 快速入门 Springboot mini - Solon详解(二)- Solon ...