In the previous article, the Fresco source code analysis – image loading process explains the whole process of image loading, but in addition to understanding the source code, the framework level of the source code design is also needed to understand, not just read the source code, good source code framework design is also worth learning. Later, when we develop a source code ourselves, we will be able to apply the good lessons learned to our own code.

The code project

At the Module level, you can see that Fresco has a number of modules, and they are divided according to the function of each module. You can tell what the module does by name.

Although there are many modules, it is not like our usual projects, where a module contains a lot of code, sometimes only a few classes, but fresco is relatively detailed.

Here are some key modules:

  • Moving-base: this is a common base of gifs, including per-frame caching, decoding rendering, frame count, width and height logic.
  • Drawee: UI layer, such as DraweeView, drawable related classes, etc.
  • Imagepipeline: the core of the whole project, image loading, memory, cache management, bitmap processing and other core logic;
  • Imagepipeline-base: is the base class of Imagepipeline, including the interface (cache, decode, image information) and related base classes;
  • Imagepipeline – backends: Here is the last request logic, here are two different sample, respectively the volly and ok – HTTP, default is HttpUrlConnectionNetworkFetcher, that is to say business parties have more complex business requirements, need yourself to achieve them.
  • Drawee-backends: Encapsulates request and initialization logic on top of Drawee, such as Fresco, PipelineDraweeController, and other related classes.

 

A Brief Introduction to design Ideas

1. There are many classes that end in config. How are they used?

ImagePipelineConfig: internal use builder mode to create, mainly the ImagePipeline required parameters are managed through config;

ImagePipelineFactory: Generates ImagePipeline from ImagePipelineConfig;

Similarly:

  • DraweeConfig: Internal use of Builder mode to create, including Drawee related configuration;
  • DiskCacheConfig: The DiskCacheConfig is internally created in Builder mode, which contains various disk configurations, including directory, version, cache size, error log, and so on. Finally, DiskStorageCacheFactory is used to generate disk.
  • PoolConfig: creates the PoolConfig file internally using the Builder mode, which will eventually be used in PoolFactory.

You can see that the Config class in Fresco uses buidler mode. So why use Builder mode? Since config is a configuration class by name, there will be many parameters in it, so the Builder mode will be adopted. In the future, when others configure, they will not care about parameter problems.

I actually have a problem with the internal Builder and that’s why we use the internal Builder.

  • One is that there are too many internal attributes, and if you use the constructor pattern, you need to write a lot of constructors
  • Second, after using builder mode, the user only needs to set the attributes he cares about, and the other attributes he doesn’t care about can be processed with the default value, which is to reduce the pressure of the user.
  • Third, once the construction is completed, it can not be modified, builder is to set the attribute, but the class itself only provides access to the attribute method, does not provide setting method, isolated from the user change to bring uncontrollable factors.
  • As for internal builders, they probably don’t want to be independent and scattered all over the place which is difficult to manage.

 

2. It provides a lot of information about how producers and consumers are managed and how to maintain the evil relationship between them.

ProducerFactory is provided to manage all producers. ProducerFactory has static methods, most of which are non-static. Mainly used to get various producers.

ProducerSequenceFactory: This is basically connecting producers together; Or put them together according to certain rules. In this way, when the external world calls, it only needs to determine what your Sequence is and call the corresponding method to obtain the Sequence. In Sequence, the ProducerFactory is used to retrieve the specified Sequence

Each producer has a corresponding consumer, and most consumers are internal classes of the producer.

 

3. What a DataSource does

DataSource is a generic interface. According to the description of the source code, it is similar to the future principle, but there is a difference, that is, it can obtain the current progress.

AbstractDataSource extends from self DataSource; The interior of this block has been maintained in various states; Notices are then made through listeners.

AbstractProducerToDataSourceAdapter: inherited from AbstractDataSource, you can see from the name that is an adapter, the Producer to the DataSource.

 

The ProducerContext function

It is mainly used to pass context information to the Producer. If you look at the code, you can see that there’s a lot of logic inside the context.

public interface ProducerContext { @StringDef({ ExtraKeys.ORIGIN, ExtraKeys.ORIGIN_SUBCATEGORY, ExtraKeys.NORMALIZED_URI, ExtraKeys.SOURCE_URI, ExtraKeys.ENCODED_WIDTH, ExtraKeys.ENCODED_HEIGHT, ExtraKeys.ENCODED_SIZE, ExtraKeys.MULTIPLEX_BITMAP_COUNT, ExtraKeys.MULTIPLEX_ENCODED_COUNT, }) @interface ExtraKeys { String ORIGIN = "origin"; String ORIGIN_SUBCATEGORY = "origin_sub"; String SOURCE_URI = "uri_source"; String NORMALIZED_URI = "uri_norm"; String ENCODED_WIDTH = "encoded_width"; String ENCODED_HEIGHT = "encoded_height"; String ENCODED_SIZE = "encoded_size"; /* number of deduped request in BitmapMemoryCacheKeyMultiplexProducer */ String MULTIPLEX_BITMAP_COUNT = "multiplex_bmp_cnt"; /* number of deduped request in EncodedCacheKeyMultiplexProducer */ String MULTIPLEX_ENCODED_COUNT = "multiplex_enc_cnt"; } /** @return image request that is being executed */ ImageRequest getImageRequest(); /** @return id of this request */ String getId(); /** @return optional id of the UI component requesting the image */ @Nullable String getUiComponentId(); /** @return ProducerListener2 for producer's events */ ProducerListener2 getProducerListener(); /** @return the {@link Object} that indicates the caller's context */ Object getCallerContext(); /** @return the lowest permitted {@link ImageRequest.RequestLevel} */ ImageRequest.RequestLevel getLowestPermittedRequestLevel(); /** @return true if the request is a prefetch, false otherwise. */ boolean isPrefetch(); /** @return priority of the request. */ Priority getPriority(); /** @return true if request's owner expects intermediate results */ boolean isIntermediateResultExpected(); /** * Adds callbacks to the set of callbacks that are executed at various points during the * processing of a request. *  * @param callbacks callbacks to be executed */ void addCallbacks(ProducerContextCallbacks callbacks); ImagePipelineConfig getImagePipelineConfig(); EncodedImageOrigin getEncodedImageOrigin(); void setEncodedImageOrigin(EncodedImageOrigin encodedImageOrigin); <E> void setExtra(@ExtraKeys String key, @Nullable E value); void putExtras(@Nullable Map<String, ? > extras); @Nullable <E> E getExtra(String key); @Nullable <E> E getExtra(String key, @Nullable E valueIfNotFound); Map<String, Object> getExtras(); /** Helper to set {@link ExtraKeys#ORIGIN} and {@link ExtraKeys#ORIGIN_SUBCATEGORY} */ void putOriginExtra(@Nullable String origin, @Nullable String subcategory); /** Helper to set {@link ExtraKeys#ORIGIN} */ void putOriginExtra(@Nullable String origin); }Copy the code

 

I’m going to use ProducerContext, which is actually interface mode, and then it’s kind of interface oriented programming, which makes it a little bit easier to extend later.

A similar context is FrescoContext.

In fact, we can also consider the context when designing the source code. The existence of context can reduce the dependency between many classes, making the code logic clearer.

 

5, ImagePipeline

ImagePipelineConfig: This can be said to be the use of ImagePipeline all things; This is for users to configure when using Fresco.

ImagePipeline: the class that initiates the request (including network, local cache, memory, callback) and decoded and undecoded images, as well as prefetch;

ImagePipelineFactory: is a singleton. That is, all request configurations are the same. But in this case, how to distinguish between different requests needs to be looked at carefully. Urls are managed through imageRequest, and ImagePipeline is mainly responsible for managing other things. All requests, including caching, should be the same. The image request is then made when getDataSourceSupplier is retrieved.

ImageRequest: imageRequest contains url and other related information. A producerSequence is constructed in ImagePipeline. In the end, in producerSequence and settableProducerContext AbstractProducerToDataSourceAdapter into a DataSource;

 

6. The relationship between ImagePipeline and producer

This is actually explained in Image line, and they’re kind of a loop on top of each other.

 

7. Use of Builder mode

ImageRequestBuilder: Used to build the ImageRequest.

AbstractDraweeControllerBuilder: use is generic, the general logic encapsulated within it;

PipelineDraweeControllerBuilder: controller logic inside;

In fact, this is mainly explained in the previous config, so I will not go into details here.

 

8. Application of Factory mode

DefaultDrawableFactory: Generates dynamic static images;

PipelineDraweeControllerFactory: one is to create the controller, one is to create internal controller;

ImagePipelineFactory: This feels a bit like a container from which all articles associated with ImagePipeline can be fetched;

There are many other factory classes as well. As far as I am concerned, factory is used mainly to shield the concrete implementation of the product, and the caller only cares about the interface of the product.