preface

Recently, I went through the draft box and saw some previous drafts. Some articles were almost written, but I didn’t have the energy to write or because of other reasons, they were rotten in the draft box. Now I decide to take out some drafts to complete or rewrite them, which is also a process of reviewing the old and learning the new.

The Fresco source code jump chain is too long to write an article, so I gave up and will now write several articles about it. Therefore, this article will only discuss one question:

  • What exactly is the complete process of loading an image?

By going through this process, you have gone through the main logic of Fresco, and have a relatively clear understanding of Fresco.

An image loading request

A piece of Fresco code

A common Fresco image-loading code we write might look something like this:

ImageRequestBuilder ImageRequestBuilder = ImageRequestBuilder. NewBuilderWithSource (" pretending to have a picture url "); . . / / create the Controller Controller = controllerBuilder. SetImageRequest (imageRequestBuilder. The build ()). The build (); draweeview.setController(controller);Copy the code

So that’s where we start.

Build a controller

Now let’s look at DraweeController creation, first of all, we through ImageRequestBuilder request information to construct the image, and then set into the controller to create an effective controller.

public AbstractDraweeController build() { ... . return buildController(); } protected AbstractDraweeController buildController() { ... . AbstractDraweeController controller = obtainController(); . . return controller; } @override protected PipelineDraweeController obtainController() {try {Override protected PipelineDraweeController obtainController() { DraweeController oldController = getOldController(); PipelineDraweeController controller; final String controllerId = generateUniqueControllerId(); if (oldController instanceof PipelineDraweeController) { controller = (PipelineDraweeController) oldController; } else { controller = mPipelineDraweeControllerFactory.newController(); // Create a data provider to provide data for the image request (from the cache, ObtainDataSourceSupplier (Controller, controllerId), controllerId, getCacheKey(), getCallerContext(), mCustomDrawableFactories, mImageOriginListener); controller.initializePerformanceMonitoring(mImagePerfDataListener, this); return controller; } finally { ... . } } public void initialize( Supplier<DataSource<CloseableReference<CloseableImage>>> dataSourceSupplier, String id, CacheKey cacheKey, Object callerContext, @Nullable ImmutableList<DrawableFactory> customDrawableFactories, @Nullable ImageOriginListener imageOriginListener) { ... . super.initialize(id, callerContext); init(dataSourceSupplier); . . } // Set the data source provider object to PipelineDraweeController Private void init(Supplier<DataSource<CloseableReference<CloseableImage>>> dataSourceSupplier) { mDataSourceSupplier = dataSourceSupplier; }Copy the code

Look again at obtainDataSourceSupplier() to determine what type of data source provider was created

AbstractDraweeControllerBuilder.java/PipelineDraweeCOntrollerBuilder.java

protected Supplier<DataSource<IMAGE>> obtainDataSourceSupplier( final DraweeController controller, final String controllerId) { ... . if (mImageRequest ! = null) {/ / because we passed the imagerequest, so will create here: Supplier: Supplier = getDataSourceSupplierForRequest (controller, controllerId. mImageRequest); } else if (mMultiImageRequests ! = null) { supplier = getFirstAvailableDataSourceSupplier( controller, controllerId, mMultiImageRequests, mTryCacheOnlyFirst); }... . return supplier; } protected Supplier<DataSource<IMAGE>> getDataSourceSupplierForRequest( final DraweeController controller, final String controllerId, final REQUEST imageRequest, final CacheLevel cacheLevel) { final Object callerContext = getCallerContext(); Return new Supplier<DataSource<IMAGE>>() {@override public DataSource<IMAGE> get() {// Finally providing a DataSource for Request, Return getDataSourceForRequest(Controller, controllerId, imageRequest, callerContext, cacheLevel); getDataSourceForRequest(Controller, controllerId, imageRequest, callerContext, cacheLevel); }... . }; }Copy the code

The build Controller process is the process of configuring the context of the current image request and the requested content provider.

We can take a quick look at the interface defined by DraweeController

Public interface DraweeController {@nullable DraweeHierarchy getHierarchy(); void setHierarchy(@Nullable DraweeHierarchy hierarchy); Void onAttach(); // Attach the view and dettch callback. void onDetach(); void onViewportVisibilityHint(boolean isVisibleInViewportHint); boolean onTouchEvent(MotionEvent event); Animatable getAnimatable(); Boolean isSameImageRequest(DraweeController other); }Copy the code

SetController behind

Next, let’s look at the draweeView.setController () interface. How does the draweeView.setController () interface trigger the image request and draw the image to the current view

DraweeView. Java,

public void setController(@Nullable DraweeController draweeController) { mDraweeHolder.setController(draweeController); / / create a general drawable, plotted on a view contains we set imageholder super. SetImageDrawable (mDraweeHolder. GetTopLevelDrawable ()); }Copy the code

There is a mDraweeHolder class, which is a key class in Fresco’s DraweeView or custom View. I explained its purpose in a previous article. If you are interested, please refer to the Fresco source code for non-invasive answers.

Here’s a quick explanation:

The DraweeViewHolder is responsible for interacting with the Image Pipeline to get the Image data and update it to the View. We will first trace the process of the Image request and then see the drawing part later.

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; if (mController ! = null) { mEventTracker.recordEvent(Event.ON_SET_CONTROLLER); mController.setHierarchy(mHierarchy); } else { mEventTracker.recordEvent(Event.ON_CLEAR_CONTROLLER); } if (wasAttached) {// Normally the view is attched, otherwise attachController() will wait for attach's callback; } } private void attachController() { if (mIsControllerAttached) { return; } mIsControllerAttached = true; if (mController ! = null && mController.getHierarchy() ! = null) { mController.onAttach(); }}Copy the code

AbstractDraweeController

@Override public void onAttach() { .... . mIsAttached = true; if (! mIsRequestSubmitted) { submitRequest(); // Submit request}... . } protected void submitRequest() { final T closeableImage = getCachedImage(); if (closeableImage ! = null) {// If there is cache, use cache. . MDataSource = getDataSource(); . . // Final DataSubscriber<T> DataSubscriber = new @override public void ondatasource (DataSource<T> DataSource) { We will not expand it here, but trace the logic of the request, which we will return to later... . }... . }; mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor); . . }Copy the code

The main job of the submitRequest above is to create an observer mode (subscriber mode) and wait for notifications to process the image. The main job is obviously left to the observer, the DataSource().

PipelineDraweeController

@Override protected DataSource<CloseableReference<CloseableImage>> getDataSource() { ... . // mDataSourceSupplier is an anonymous inner class that is passed to PipelineDraweeController when constructing the information and logic in the Build Controller step. DataSource<CloseableReference<CloseableImage>> result = mdatasourcesupplier.get (); . . return result; } // mDataSourceSupplier objects come from this method,  // AbstractDraweeControllerBuilder.java protected Supplier<DataSource<IMAGE>> getDataSourceSupplierForRequest( final DraweeController controller, final String controllerId, final REQUEST imageRequest, final CacheLevel cacheLevel) { final Object callerContext = getCallerContext(); Return new Supplier<DataSource<IMAGE>>() {// mdatasourcesupplier.get () calls the get() method of this anonymous inner class @override public DataSource<IMAGE> get() {return datasourceForRequest (controller, controllerId, imageRequest, callerContext, cacheLevel); }}; }Copy the code

PipelineDraweeControllerBuilder.java

@Override 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); }Copy the code

ImagePipeline.java

// Submit the image request and return the DataSource, Public DataSource<CloseableReference<CloseableImage>> fetchDecodedImage(ImageRequest, Object callerContext, ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit, @nullable RequestListener RequestListener, @nullable String uiComponentId) {try {// Get producer, A Producer is a data Producer of an image. Used to obtain data from the cache or network Producer<CloseableReference<CloseableImage>> producerSequence = mProducerSequenceFactory.getDecodedImageProducerSequence(imageRequest); return submitFetchRequest( producerSequence, imageRequest, lowestPermittedRequestLevelOnSubmit, callerContext, requestListener, uiComponentId); } catch (Exception exception) { return DataSources.immediateFailedDataSource(exception); } } private <T> DataSource<CloseableReference<T>> submitFetchRequest( Producer<CloseableReference<T>> producerSequence, ImageRequest imageRequest, ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit, Object callerContext, @Nullable RequestListener requestListener, @Nullable String uiComponentId) { ... . // Create context information for production, SettableProducerContext SettableProducerContext = new SettableProducerContext(imageRequest, generateUniqueFutureId(), uiComponentId, requestListener2, callerContext, lowestPermittedRequestLevel, /* isPrefetch */ false, imageRequest.getProgressiveRenderingEnabled() || ! UriUtil.isNetworkUri(imageRequest.getSourceUri()), imageRequest.getPriority(), mConfig); Created a CloseableProducerToDataSourceAdapter adapter / /, / / producer for adaptation and the DataSource, / / which means producerSequence production data through adaptation, Sent to the DataSource return CloseableProducerToDataSourceAdapter. Create (producerSequence settableProducerContext, requestListener2); }Copy the code

Look briefly at the definition of producer’s interface

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). * * @param consumer * @param context */ / Void produceResults(consumer <T> consumer, ProducerContext context); }Copy the code

CloseableProducerToDataSourceAdapter skill from the producer to get the image data are available, and then as a DataSource to inform its subscribers (observers).

protected AbstractProducerToDataSourceAdapter( Producer<T> producer, SettableProducerContext settableProducerContext, RequestListener2 requestListener) { ... . // This is the interface for producing data from producer. ProduceResults (createConsumer(), settableProducerContext); createConsumer() is the interface that initiates the request. } private Consumer<T> createConsumer() {return new BaseConsumer<T>() {@override protected void onNewResultImpl(@Nullable T newResult, @Status int status) { AbstractProducerToDataSourceAdapter.this.onNewResultImpl(newResult, status); } @Override protected void onFailureImpl(Throwable throwable) { AbstractProducerToDataSourceAdapter.this.onFailureImpl(throwable); } @Override protected void onCancellationImpl() { AbstractProducerToDataSourceAdapter.this.onCancellationImpl(); } @Override protected void onProgressUpdateImpl(float progress) { AbstractProducerToDataSourceAdapter.this.setProgress(progress); }}; } protected void onNewResultImpl(@Nullable T result, int status) { boolean isLast = BaseConsumer.isLast(status); If (super.setresult (result, isLast)) {// setResult triggers notifyDataSubscribers(), notifying the subscriber that the data is ready. . } } protected boolean setResult(@Nullable T value, boolean isLast) { boolean result = setResultInternal(value, isLast); if (result) { notifyDataSubscribers(); } return result; }Copy the code

Now the question remains, what is the implementation of the producer? We call back above to get the producer position

ProducerSequenceFactory.java

public Producer<CloseableReference<CloseableImage>> getDecodedImageProducerSequence( ImageRequest imageRequest) { ... . Producer<CloseableReference<CloseableImage>> pipelineSequence = getBasicDecodedImageSequence(imageRequest); return pipelineSequence; } private Producer<CloseableReference<CloseableImage>> getBasicDecodedImageSequence( ImageRequest imageRequest) { Uri uri = imageRequest.getSourceUri(); / / based on the uri of the corresponding type producer switch (imageRequest. GetSourceUriType ()) {case SOURCE_TYPE_NETWORK: // Since we are sending an HTTP link, networkProducer return getNetworkFetchSequence(); case SOURCE_TYPE_LOCAL_VIDEO_FILE: return getLocalVideoFileFetchSequence(); case SOURCE_TYPE_LOCAL_IMAGE_FILE: return getLocalImageFileFetchSequence(); case SOURCE_TYPE_LOCAL_CONTENT: if (MediaUtils.isVideo(mContentResolver.getType(uri))) { return getLocalVideoFileFetchSequence(); } return getLocalContentUriFetchSequence(); case SOURCE_TYPE_LOCAL_ASSET: return getLocalAssetFetchSequence(); case SOURCE_TYPE_LOCAL_RESOURCE: return getLocalResourceFetchSequence(); case SOURCE_TYPE_QUALIFIED_RESOURCE: return getQualifiedResourceFetchSequence(); case SOURCE_TYPE_DATA: return getDataFetchSequence(); . } } private synchronized Producer<CloseableReference<CloseableImage>> getNetworkFetchSequence() { if (mNetworkFetchSequence == null) { mNetworkFetchSequence = newBitmapCacheGetToDecodeSequence(getCommonNetworkFetchToEncodedMemorySequence()); } return mNetworkFetchSequence; } // multiplex -> encoded cache -> disk cache -> (webp transcode) -> network fetch private synchronized Producer<EncodedImage> getCommonNetworkFetchToEncodedMemorySequence() { if (mCommonNetworkFetchToEncodedMemorySequence MNetworkFetcher == null) {// MNetworkProducer is used to request picture data from the network. Will use the default library network request Producer < EncodedImage > inputProducer = newEncodedCacheMultiplexToTranscodeSequence ( mProducerFactory.newNetworkFetchProducer(mNetworkFetcher)); mCommonNetworkFetchToEncodedMemorySequence = ProducerFactory.newAddImageTransformMetaDataProducer(inputProducer); // Create some other Producer that does other things to the picture. Here temporarily not speak mCommonNetworkFetchToEncodedMemorySequence = mProducerFactory. NewResizeAndRotateProducer ( mCommonNetworkFetchToEncodedMemorySequence, mResizeAndRotateEnabledForNetwork && ! mDownsampleEnabled, mImageTranscoderFactory); } return mCommonNetworkFetchToEncodedMemorySequence; }Copy the code

What we see is the creation of multiple producers. How are these producers organized and called in order? I don’t have a lot of space here, but in a nutshell, the way they’re organized is one ring at a time

The overall structure of Producers

When tracing how requests are sent one by one, we can see that many producers are organized in a similar way to an assembly line. The upper producers hold the references of the lower producers’ objects, and the lower producers hold the callback of the upper producers’ consumers. Its basic structure is shown as follows:

When a series of producers are combined, their structure looks like this:

This is the organizational structure of the producer series, which we will probably examine in detail in the next article.

Request pictures from the network

From the first call CloseableProducerToDataSourceAdapter

ProduceResults (createConsumer(), settableProducerContext);Copy the code

After that, a series of reasonably organized producers are called in order. If no data can be found in the cached producers, NetworkFetchProducer is called to perform the final processing.

NetworkFetchProducer

@Override public void produceResults(Consumer<EncodedImage> consumer, ProducerContext context) { context.getProducerListener().onProducerStart(context, PRODUCER_NAME); final FetchState fetchState = mNetworkFetcher.createFetchState(consumer, context); mNetworkFetcher.fetch( fetchState, new NetworkFetcher.Callback() { @Override public void onResponse(InputStream response, int responseLength) throws IOException { NetworkFetchProducer.this.onResponse(fetchState, response, responseLength); } @Override public void onFailure(Throwable throwable) { NetworkFetchProducer.this.onFailure(fetchState, throwable); } @Override public void onCancellation() { NetworkFetchProducer.this.onCancellation(fetchState); }}); Protected void onResponse(FetchState FetchState, InputStream responseData, int responseContentLength) throws IOException { final PooledByteBufferOutputStream pooledOutputStream; if (responseContentLength > 0) { pooledOutputStream = mPooledByteBufferFactory.newOutputStream(responseContentLength); } else { pooledOutputStream = mPooledByteBufferFactory.newOutputStream(); } final byte[] ioArray = mByteArrayPool.get(READ_SIZE); try { int length; While ((length = responseData.read(ioArray)) >= 0) {if (length > 0) {pooledOutputStream.write(ioArray, 0, responseData.read(ioArray)) {pooledOutputStream.write(ioArray, 0, responseData.read(ioArray)); length); maybeHandleIntermediateResult(pooledOutputStream, fetchState); float progress = calculateProgress(pooledOutputStream.size(), responseContentLength); Fetchstate.getconsumer ().onProgressupDate (progress); } } mNetworkFetcher.onFetchCompletion(fetchState, pooledOutputStream.size()); HandleFinalResult (pooledOutputStream, fetchState); } finally { ... . }}Copy the code

A common request, NetworkFetcher as implementation class library the basis of common network request has two HttpUrlConnectionNetworkFetcher (default), OkHttpNetworkFetcher

Let’s see how the full data is called back to the top, okay

protected void handleFinalResult( PooledByteBufferOutputStream pooledOutputStream, FetchState fetchState) { Map<String, String> extraMap = getExtraMap(fetchState, pooledOutputStream.size()); . . notifyConsumer( pooledOutputStream, Consumer.IS_LAST | fetchState.getOnNewResultStatusFlags(), fetchState.getResponseBytesRange(), fetchState.getConsumer(), fetchState.getContext()); } protected static void notifyConsumer( PooledByteBufferOutputStream pooledOutputStream, @Consumer.Status int status, @Nullable BytesRange responseBytesRange, Consumer<EncodedImage> consumer, ProducerContext context) { CloseableReference<PooledByteBuffer> result = CloseableReference.of(pooledOutputStream.toByteBuffer()); EncodedImage encodedImage = null; EncodedImage = new encodedImage (result); encodedImage.setBytesRange(responseBytesRange); / / if you order in fresco "resolution image information, you will find that the fresco" kind tell you there is a bug encodedImage. ParseMetaData (); context.setEncodedImageOrigin(EncodedImageOrigin.NETWORK); // Consumer is the consumer callback. OnNewResult (encodedImage, status); } finally { ... . }}Copy the code

The callback starts from the bottom NetworkFetchProducer, and the producer does some processing in between (more on that in the next article). And the callback to the upper AbstractProducerToDataSourceAdapter createConsumer () here, and then through the setResult () to notify the subscriber, complete the data acquisition of notification.

Let’s write the subscriber callback method below and see how the resulting image data is drawn to the view.

. . MDataSource = getDataSource(); final DataSubscriber<T> dataSubscriber = new BaseDataSubscriber<T>() { @Override public void OnNewResultImpl (DataSource<T> DataSource) {Boolean isFinished = DataSource. IsFinished (); boolean hasMultipleResults = dataSource.hasMultipleResults(); float progress = dataSource.getProgress(); T image = dataSource.getResult(); if (image ! OnNewResultInternal (ID, dataSource, image, Progress, isFinished, wasImmediate, hasMultipleResults); }... . }... . }; private void onNewResultInternal( String id, DataSource<T> dataSource, @Nullable T image, float progress, boolean isFinished, boolean wasImmediate, boolean deliverTempResult) { try { ... . Drawable drawable; Drawable = createDrawable(image); } catch (Exception exception) { ... . } T previousImage = mFetchedImage; Drawable previousDrawable = mDrawable; mFetchedImage = image; mDrawable = drawable; If (isFinished) {logMessageAndImage("set_final_result @onnewresult ", image); mDataSource = null; / / the drawable encapsulates the pictures to the DraweeHierarchy preparing mSettableDraweeHierarchy. SetImage (drawable, 1 f, wasImmediate); getControllerListener().onFinalImageSet(id, getImageInfo(image), getAnimatable()); }... . } finally {// Drawable if (previousDrawable! = null && previousDrawable ! = drawable) { releaseDrawable(previousDrawable); } if (previousImage ! = null && previousImage ! = image) { logMessageAndImage("release_previous_result @ onNewResult", previousImage); releaseImage(previousImage); } } } finally { ... . }}Copy the code

We get the image, encapsulate it into a drawable, and pass the data to DraweeHierarchy to update the drawable, which also confirms our structure division above.

We’ll go to GenericDraweeHierarchy, the only implementation of DraweeHierarchy, and see what happens to the drawable.

GenericDraweeHierarchy.java

// TopLevelDrawable is the Drawable that DraweeHolder first sets to display in View mTopLevelDrawable; // ForwardingDrawable is one of TopLevelDrawable's drawable, Private Final ForwardingDrawable mActualImageWrapper; @Override public void setImage(Drawable drawable, float progress, boolean immediate) { ... . // GenericDraweeHierarchy holds a lot of layer Drawable via TopLevelDrawable // ForwardingDrawable is one of the layer drawable, mActualImageWrapper setDrawable (drawable); . . }Copy the code

At this point, we’ve finally completed the entire process from sending an image request to drawing it onto the View.

conclusion

Just to review, there are two main operations

  • build DraweeController
  • setcontroller

The first operation configures the information and execution logic for the image

The second operation is to bind the picture to the corresponding view, ensure that the picture can be drawn to the corresponding view, and create a series of producer, orderly execution and callback.

errata

no