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 dataSubscriber = new BaseDataSubscriber() { //可以简单的把它理解为一个监听者
        @Override
        public void onNewResultImpl(DataSource dataSource) { //图片加载成功
            ...
        }
        ...
    };
    ...
    mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor); //mUiThreadImmediateExecutor是指 dataSubscriber 回调方法运行的线程,这里是主线程
}

DataSource  是什么呢? getDataSource() 最终会调用到:

// PipelineDraweeControllerBuilder 
 protected DataSource> getDataSourceForRequest(
      DraweeController controller,
      String controllerId,
      ImageRequest imageRequest,
      Object callerContext,
      AbstractDraweeControllerBuilder.CacheLevel cacheLevel) {
    return mImagePipeline.fetchDecodedImage(
        imageRequest,
        callerContext,
        convertCacheLevelToRequestLevel(cacheLevel),
        getRequestListener(controller),
        controllerId);
  }
// CloseableProducerToDataSourceAdapter
public static  DataSource> create(
      Producer> producer,
      SettableProducerContext settableProducerContext,
      RequestListener2 listener) {
   
    CloseableProducerToDataSourceAdapter result =
        new CloseableProducerToDataSourceAdapter(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 dataSubscriber = new BaseDataSubscriber() { //可以简单的把它理解为一个监听者
        @Override
        public void onNewResultImpl(DataSource dataSource) { //图片加载成功
            ...
        }
        ...
    };
    ...
    mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor); //mUiThreadImmediateExecutor是指 dataSubscriber 回调方法运行的线程,这里是主线程
}

即通过 submitRequest() 提交了一个请求,这个方法我们前面已经看过了,它所做的主要事情就是,构造了一个 DataSource 。这个  DataSource  我们经过追踪,它的实例实际上是 CloseableProducerToDataSourceAdapterCloseableProducerToDataSourceAdapter  在构造时就会调用  producer.produceResults(...), 进而发起整个图片加载流程。

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

到这里我们梳理完了 Fresco  在真正发起图片加载前所走的逻辑,那么  Fresco  的图片加载流程是如何控制的呢?到底经历了哪些步骤呢?

Producer

Fresco 中有关图片的内存缓存、解码、编码、磁盘缓存、网络请求都是在这一层实现的,而所有的实现的基本单元是  Producer, 所以我们先来理解一下  Producer :

看一下它的定义:

/**
 * 

Execution of image request consists of multiple different tasks such as network fetch, * disk caching, memory caching, decoding, applying transformations etc. Producer 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 { /** * Start producing results for given context. Provided consumer is notified whenever progress is made (new value is ready or error occurs). */ void produceResults(Consumer consumer, ProducerContext context); }

结合注释我们可以这样定义 Producer  的作用: 一个  Producer  用来处理整个  Fresco  图片处理流程中的一步,比如从网络获取图片、内存获取图片、解码图片等等 。而对于  Consumer  可以把它理解为监听者,看一下它的定义:

public interface Consumer {
/**
   * Called by a producer whenever new data is produced. This method should not throw an exception.
   *
   * 

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> {

    private final Producer> mInputProducer;

    // 我们假设 inputProducer 在这里为NetworkFetchProducer
    public BitmapMemoryCacheProducer(...,Producer> inputProducer) { 
        ...
        mInputProducer = inputProducer;
    }

    @Override
    public void produceResults(Consumer> consumer,...) {
        CloseableReference cachedReference = mMemoryCache.get(cacheKey);

        if (cachedReference != null) { //从缓存中获取成功,直接通知外界
            consumer.onNewResult(cachedReference, BaseConsumer.simpleStatusForIsLast(isFinal));
            return; //结束处理流程
        }

        Consumer> wrappedConsumer = wrapConsumer(consumer..); //包了一层Consumer,即mInputProducer产生结果时,它自己可以观察到
        mInputProducer.produceResults(wrappedConsumer, producerContext); //网络加载
    }
}
// NetworkFetchProducer.java

public class NetworkFetchProducer implements Producer {

   // 它并没有 inputProducer, 对于 Fresco 的图片加载来说如果网络都获取失败,那么就是图片加载失败了

    @Override
    public void produceResults(final Consumer> consumer,..) {

       // 网路获取
       //   ...
        if(获取到网络图片){
            notifyConsumer(...); //把结果通知给consumer,即观察者
        }
        ...
    }
}

代码可能不是很好理解,可以结合下面这张图来理解这个关系:

Fresco  可以通过组装多个不同的  Producer  来灵活的定义不同的图片处理流程的,多个  Producer  组装在一块称为  ProducerSequence (Fresco 中并没有这个类哦) 。一个 ProducerSequence  一般定义一种图片处理流程,比如网络加载图片的  ProducerSequence  叫做  NetworkFetchSequence, 它包含多个不同类型的  Producer

网络图片加载的处理流程

Fresco  中不同的图片请求会有不同的  ProducerSequence  来处理,比如网络图片请求:

// ProducerSequenceFactory.java
private Producer> 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> getNetworkFetchSequence() {
    ...
    mNetworkFetchSequence = new BitmapCacheGetToDecodeSequence(getCommonNetworkFetchToEncodedMemorySequence());
    ...
    return mNetworkFetchSequence;
}

getNetworkFetchSequence  会经过重重调用来组合多个  Producer 。这里我就不追代码逻辑了,直接用下面这张图来描述  Fresco  网络加载图片的处理流程:

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

总结

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

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

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