This article is the first in a series of Fresco source code analyses, focusing on the overall architecture of Fresco, the functions of its components, and the process of loading images. The purpose of this article is to get a general understanding of the overall design of Fresco and lay the foundation for further analysis.

The Fresco source code is huge and involves a lot of image loading. This series of Fresco source analysis follows the Fresco network image loading point.

Fresco’s overall architecture

The composition of Fresco is fairly clear, as shown below:

The following code is used to explain the functions of the above modules and their working principles.

The UI layer

DraweeView

It inherits from ImageView and is used to display Fresco images during various stages of loading, such as loading a placeholder image and switching to a target image when the image is successfully loaded. However, the class may no longer inherit ImageView. The only current interaction between DraweeView and ImageView is that it uses the ImageView to display Drawable:

//DraweeView.setController()
public void setController(@Nullable DraweeController draweeController) { mDraweeHolder.setController(draweeController); super.setImageDrawable(mDraweeHolder.getTopLevelDrawable()); / / is super ImageView} / / DraweeHolder getTopLevelDrawable () public @ Nullable DrawablegetTopLevelDrawable() {
    returnmHierarchy == null ? null : mHierarchy.getTopLevelDrawable(); MHierarchy is DraweeHierachy}Copy the code

Draweeview.setcontroller () is called when Fresco loads the image. Fresco uses ImageView to display hierachy’s TopLevelDrawable. The above code leads to two other key classes in the UI layer :DraweeHolder and DraweeHierachy.

DraweeHierachy

It is the implementer of the Fresco image display. Its output is a Drawable, which is drawn by the DraweeView (as described above). It has multiple Drawable inside, and the Drawable currently displayed in DraweeView is called TopLevelDrawable. The TopLevelDrawable is different in different image loading stages (e.g. placeholder during loading and target image after loading). It implements the Drawable logic.

It is held directly by DraweeController, so switching between images is performed directly by DraweeController.

DraweeHolder

It maintains the attach relationship between DraweeView and DraweeController (DraweeView can only load network images if attch is used by DraweeController). It can be understood as the glue between DraweeView, DraweeHierachy and DraweeController classes. The specific reference relationship is shown below:

DraweeController: Loads the logical control layer

Its main functions are: Receive image loading request of DraweeView, control ProducerSequence to initiate image loading and processing process, monitor events (failure, completion, etc.) in the loading process of ProducerSequence, and update the latest Drawable to DraweeHierachy.

The construction logic of DraweeController

In Fresco the DraweeController is constructed using the DraweeControllerBuilder. The DraweeControllerBuilder exists as a singleton in Fresco. Fresco calls the following code when it initializes:

Fresco.java

private static void initializeDrawee(Context context, @Nullable DraweeConfig draweeConfig) {
    sDraweeControllerBuilderSupplier = new PipelineDraweeControllerBuilderSupplier(context, draweeConfig);
    SimpleDraweeView.initialize(sDraweeControllerBuilderSupplier);
}
Copy the code

So all draweecontrollers are constructed from the same DraweeControllerBuilder. Fresco provides a DraweeController for each image load. The same DraweeController can be used for multiple images loaded from a DraweeView:

SimpleDraweeView.java

public void setImageURI(Uri uri, @Nullable Object callerContext) {
    DraweeController controller =
        mControllerBuilder
            .setCallerContext(callerContext).seturi (uri) // Set the new image loading path.setoldController (getController()) // reuse controller.build ();setController(controller);
}
Copy the code

So in general: one DraweeView for one DraweeController.

Initiate image loading via DataSource

As mentioned above, DraweeController holds DraweeHierachy directly, so it can easily update ProducerSequence data changes to DraweeHierachy (the specific code is not shown for now). How does it control the ProducerSequence to load the image? The DraweeController is not directly associated with the ProducerSequence. For image loading, it directly contacts the DataSource, and the DataSource controls the ProducerSequence to initiate image loading and processing. The DraweeController uses DataSource to control the ProducerSequence to load and process images.

DraweeController launch image loading request method is (AbstractDraweeController. Java) :

protected void submitRequest() { mDataSource = getDataSource(); Final DataSubscriber<T> DataSubscriber = new BaseDataSubscriber<T>() OnNewResultImpl (DataSource<T> DataSource) { }... }; . mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor); / / mUiThreadImmediateExecutor refers to dataSubscriber callback methods running thread, this is the main thread}Copy the code

So what is a DataSource? GetDataSource () will eventually call:

ImagePipeline.java

public DataSource<CloseableReference<CloseableImage>> fetchDecodedImage(ImageRequest imageRequest,...) {// Get ProducerSequence Producer<CloseableReference<CloseableImage>> ProducerSequence = mProducerSequenceFactory.getDecodedImageProducerSequence(imageRequest);return submitFetchRequest(
          producerSequence,
          imageRequest,
          lowestPermittedRequestLevelOnSubmit,
          callerContext, requestListener); } private <T> DataSource<CloseableReference<T>> submitFetchRequest(...) {...return CloseableProducerToDataSourceAdapter.create(roducerSequence, settableProducerContext, finalRequestListener);
}
Copy the code

So DraweeController eventually get the DataSource is CloseableProducerToDataSourceAdapter. The class constructor calls producer.produceresults (…) to start the image-loading process. This method is the starting point for loading images, which we will see later.

In Fresco, the DraweeController creates a DataSource for each image load. The DataSource is used to provide the requested data. The DataSource is just an interface, but the loading process is implemented by the ProducerSequence Fresco.

Fresco image logic before loading

With that in mind, let’s go through the image-loading source code (from UI to DraweeController) to sort out the connections between the modules as we know them so far. When we use Fresco “loading images are generally use this API: SimpleDraweeView. SetImageURI (imageLink), this method will eventually call to:

SimpleDraweeView.java

public void setImageURI(Uri uri, @Nullable Object callerContext) {
    DraweeController controller = mControllerBuilder
            .setCallerContext(callerContext) .setUri(uri) .setOldController(getController()) .build(); // The controller will be reused heresetController(controller);
}

public void setController(@Nullable DraweeController draweeController) {
    mDraweeHolder.setController(draweeController);
    super.setImageDrawable(mDraweeHolder.getTopLevelDrawable());  
}
Copy the code

DraweeControllerBuilder is used to build a DraweeController for each load. Actually, this DraweeController is multiplexed by default. The DraweeController is then set to the DraweeHolder and the TopLevelDrawable is fetched from the DraweeHolder by default at the start of loading and displayed to the DraweeView. Continuing with DraweeHolder’s logic:

DraweeHolder.java

public @Nullable Drawable getTopLevelDrawable() {
    return mHierarchy == null ? null : mHierarchy.getTopLevelDrawable();
}

public void setController(@Nullable DraweeController draweeController) { detachController(); mController = draweeController; . mController.setHierarchy(mHierarchy); attachController(); }Copy the code

In DraweeHolder. SetController () give DraweeController DraweeHierachy Settings, and attachController (), attachController () call DraweeCont roller.onAttach():

AbstractDraweeController.java

public void onAttach() {... mIsAttached =true;
    if(! mIsRequestSubmitted) { submitRequest(); } } protected voidsubmitRequest() { mDataSource = getDataSource(); Final DataSubscriber<T> DataSubscriber = new BaseDataSubscriber<T>() OnNewResultImpl (DataSource<T> DataSource) { }... }; . mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor); / / mUiThreadImmediateExecutor refers to dataSubscriber callback methods running thread, this is the main thread}Copy the code

Submit a request via submitRequest(), which we’ve already seen, and the main thing it does is construct a DataSource. The DataSource we passed the tracking and its instance is actually CloseableProducerToDataSourceAdapter. CloseableProducerToDataSourceAdapter at construction time is called producer. ProduceResults (…). And then launch the whole picture loading process.

Summarize the image-loading logic from SimpleDraweeView->DraweeController with the following diagram:

Now that we’ve covered the logic of Fresco before it actually starts loading images, how does the Fresco image loading process work? What steps did you go through?

Image loading implementation layer

In Fresco, the in-memory caching, decoding, encoding, disk caching, and network requests are all implemented there. The basic unit of Fresco implementations is Producer.

Producer

Take a look at its definition:

/**
 * <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);
}
Copy the code

The role of a Producer can be defined as follows: A Producer handles a step in the Fresco process, such as getting the picture from the network, getting the picture from memory, decoding the picture, and so on. A Consumer is a listener. Let’s look at its definition:

public interface Consumer<T> { ... void onNewResult(T newResult, @Status int status); Void onFailure(Throwable t); //Producer fails to process... }Copy the code

The result of the Producer’s processing can be used by the Consumer to tell the outside world, for example, whether the Producer failed or succeeded.

The combination of the Producer

One ProducerA can accept another ProducerB as a parameter, and if ProducerA is finished, ProducerB can be called to continue processing. And pass Consumer to observe ProducerB processing results. For example, Fresco loads images from the in-memory cache, or loads them from the network if they are not in the in-memory cache. Here involves two Producer BitmapMemoryCacheProducer and NetworkFetchProducer, assuming BitmapMemoryCacheProducer ProducerA, NetworkFetchProducer ProducerB. Let’s look at their logic in pseudocode:

BitmapMemoryCacheProducer.java

public class BitmapMemoryCacheProducer implements Producer<CloseableReference<CloseableImage>> { private final Producer<CloseableReference<CloseableImage>> mInputProducer; / / we assume inputProducer here for 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) {/ / from the cache to be successful, directly to inform the outside world. Consumer onNewResult (cachedReference, BaseConsumer simpleStatusForIsLast (isFinal));return; Consumer<CloseableReference<CloseableImage>> wrappedConsumer = wrapConsumer(Consumer..) ; / / package with a layer of Consumer, namely mInputProducer results, it can be observed mInputProducer. ProduceResults (wrappedConsumer producerContext); // Network loading}}Copy the code

NetworkFetchProducer.java

Public class NetworkFetchProducer implements Producer<EncodedImage> {it does not inputProducer. @override public void produceResults(final Consumer<CloseableReference<CloseableImage>> Consumer) {Network access...if{notifyConsumer(...) ; // Notify the consumer of the result}... }}Copy the code

The code may not be easy to understand, but you can use the following diagram to understand the relationship:

Fresco can flexibly define different picture processing flows by assembling producers into a ProducerSequence(there is no such class). A ProducerSequence generally defines a picture processing process. For example, a ProducerSequence that loads pictures from the network is called NetworkFetchSequence and contains multiple types of Producer.

Network image loading process

Different image requests in Fresco have different ProducerSequence processes, such as web image requests:

ProducerSequenceFactory.java

private Producer<CloseableReference<CloseableImage>> getBasicDecodedImageSequence(ImageRequest imageRequest) {
    switch (imageRequest.getSourceUriType()) {
        case SOURCE_TYPE_NETWORK: returngetNetworkFetchSequence(); . }Copy the code

So for network image requests getNetworkFetchSequence is called:

/**
* 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;
}
Copy the code

The getNetworkFetchSequence uses multiple calls to compose multiple producers. Instead of following the code logic, I’ll use the following diagram to illustrate the Fresco network image loading process:

You can see how complex the Fresco image loading process is. In the figure above, I just list some key producers. In fact, I have not drawn some producers. If you are interested, you can explore the source code in detail.

OK, that’s the end of the article, and hopefully it gave you a sense of the Fresco design as a whole. Future articles will continue to discuss Fresco’s cache logic, image compression, DraweeHierachy’s Drawable switching logic, and more. Stay tuned.

I am currently working in Xiaohongshu. If you are interested in joining the xiaohongshu client team, please send your resume to[email protected]

Welcome to mineAndroid Advanced PlansSee more dry goods

Welcome to follow my wechat official account: Susion Heart