Fresco 源码分析 —— 整体架构

Fresco 是我们项目中图片加载专用框架。虽然我不是负责 Fresco 框架,但是由本人负责组里的图片加载浏览等工作,因此了解 Fresco 的源码有助于我今后的工作,也可以学习 Fresco 的源码设计精髓。

由于 Fresco 源码比较多,仅凭一篇文章是无法将其说清楚的,因此会当做一个系列,详细介绍 Fresco 源码。本系列文章也会参考网上关于 Fresco 源码解析的文章,尽可能准确的去描述 Fresco 的实现原理,如有错误之处欢迎指出,欢迎交流学习。

Fresco 是一个强大的图片加载组件。使用它之后,你不需要再去关心图片的加载和显示这些繁琐的事情! 支持 Android 2.3 及以后的版本。如果需要了解 Fresco 的使用可以访问 Fresco 使用文档 。

Fresco是一个功能完善的图片加载框架,在Android开发中有着广泛的应用,那么它作为一个图片加载框架,有哪些特色让它备受推崇呢?

  • 完善的内存管理功能,减少图片对内存的占用,即便在低端机器上也有着不错的表现。

  • 自定义图片加载的过程,可以先显示低清晰度图片或者缩略图,加载完成后再显示高清图,可以在加载的时候缩放和旋转图片。

  • 自定义图片绘制的过程,可以自定义谷中焦点、圆角图、占位图、overlay、进图条。

  • 渐进式显示图片。

  • 支持Gif。

  • 支持Webp。

  • ......

Fresco 的组成结构还是比较清晰的,大致如下图所示:

Fresco 源码分析 ——  整体架构      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

可以把它理解为 DraweeViewDraweeHierachy 和 DraweeController 这 3 个类之间的粘合剂, DraweeView 并不直接和 DraweeController 和 DraweeHierachy 直接接触,所有的操作都是通过它传过去。这样,后续将 DraweeView 的父类改为 View,也不会影响到其他类。DraweeView 作为 View 可以感知点击和生命周期,通过 DraweeHolder 来控制其他两个类的操作。

想想如果是你,你会抽出 DraweeHolder 这样一个类吗?实际上,这里对我们平时开发也是有所借鉴,严格控制每一个类之间的关系,可以引入一些一些中间类,让类与类之间的关系耦合度降低,方便日后迭代。

具体引用关系如下图:

Fresco 源码分析 ——  整体架构

它的主要功能是: 接收 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 我们经过追踪,它的实例实际上是CloseableProducerToDataSourceAdapterCloseableProducerToDataSourceAdapter 在构造时就会调用 producer.produceResults(...),进而发起整个图片加载流程。

用下面这张图总结从SimpleDraweeView->DraweeController的图片加载逻辑:

Fresco 源码分析 ——  整体架构

到这里我们梳理完了 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 为 ProducerANetworkFetchProducer 为 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 源码分析 ——  整体架构

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 源码分析 ——  整体架构

可以看到 Fresco 的整个图片加载过程还是十分复杂的。并且上图我只是罗列一些关键的 Producer,其实还有一些我没有画出来。

总结

为了辅助理解,再提供一张总结的流程图,将上面整个过程都放在里面了。后续的系列文章会详细介绍 UI 和图片加载过程,希望通过阅读其源码来详细了解内部的代码逻辑以及设计思路。

Fresco 源码分析 ——  整体架构

其实我们在阅读别人源码的时候,除了要知道具体的细节之外,也要注意别人的模块设计,借鉴其设计思想。然后想想如果是你在设计的时候,你会怎么划分模块,如何将不同的模块联系起来。

当模块划分后,里面的子模块又是如何划分的,它们之间协作关系如何保持。

 

参考文章

Android开源框架源码鉴赏:Fresco

Fresco架构设计赏析

上一篇:Xcode 真机无法调试


下一篇:Django中CBV及其源码解释