SoFunction
Updated on 2025-04-06

Flutter loading image process MultiFrameImageStreamCompleter parsing

MultiFrameImageStreamCompleter

MultiFrameImageStreamCompleterIt is a comboableImageStreamCompleterclass, used to transfer multipleImageStreamCompleterMerge objects into a separateImageStreamObject, usually used for animation effects. Whenever the childImageStreamCompleterReceived a new oneImageInfoObject, it immediately notifies all its listeners and passes the objects to them.

whenMultiFrameImageStreamCompleterofaddListener()When the method is called, it will pass inImageStreamListenerThe child added to its internalImageStreamCompleterin the List of Listeners. ifMultiFrameImageStreamCompleterReceived aImageInfoobject, it will pass it to all its listeners. However, it does not manage these frames by itself, but delegates to each childImageStreamCompleterCome and finish.

MultiFrameImageStreamCompleterIt also supports progressive JPEG and implementsaddListener()removeListener()anddispose()Method, and agetNextFrame()The method is used to obtain the next frame from the image stream.

When all frames are loaded,MultiFrameImageStreamCompleterWill usedart:The decoder combines them into a singledart:object, and pass it tosetImage()method. Finally, it will notify all listeners and pass them to()Callback functions to notify them of newImageInfoAlready available.

whenMultiFrameImageStreamCompleterofdispose()When a method is called, it will take all its childrenImageStreamCompleterofdispose()The methods are called in turn to free up all resources and cancel all unprocessed frame requests. At the same time, it also ensures that all errors are notified to its listener before releasing the resource.

_handleCodecReady

void _handleCodecReady( codec) {
  _codec = codec;
  assert(_codec != null);
  if (hasListeners) {
    _decodeNextFrameAndSchedule();
  }
}

exist_handleCodecReadyIn the method, first thecodecAssign object to member variables_codec, then useassertStatement to ensure that the variable is not empty. Next, if the current object has a listener, it is called_decodeNextFrameAndScheduleMethod to decode the next frame and schedule it to execute. The purpose here is to start decoding the next frame of images as soon as possible to show the complete animation effect as soon as possible. If there is no listener, you don't need to decode the next frame of the image, because there is no place to show it.

_decodeNextFrameAndSchedule

Future<void> _decodeNextFrameAndSchedule() async {
  // This will be null if we gave it away. If not, it's still ours and it
  // must be disposed of.
  _nextFrame?.();
  _nextFrame = null;
  try {
    _nextFrame = await _codec!.getNextFrame();
  } catch (exception, stack) {
    reportError(
      context: ErrorDescription('resolving an image frame'),
      exception: exception,
      stack: stack,
      informationCollector: _informationCollector,
      silent: true,
    );
    return;
  }
  if (_codec!.frameCount == 1) {
    // ImageStreamCompleter listeners removed while waiting for next frame to
    // be decoded.
    // There's no reason to emit the frame without active listeners.
    if (!hasListeners) {
      return;
    }
    // This is not an animated image, just return it and don't schedule more
    // frames.
    _emitFrame(ImageInfo(
      image: _nextFrame!.(),
      scale: _scale,
      debugLabel: debugLabel,
    ));
    _nextFrame!.();
    _nextFrame = null;
    return;
  }
  _scheduleAppFrame();
}
  • The purpose of this method is to get the next frame and schedule the decoding of the next frame after the acquisition is successful. If the number of frames is 1, that is, this is a static picture, you only need to return the frame and return directly when there is no listener. If the number of frames is greater than 1, schedule the decoding of the next frame.
  • Before getting the next frame, the method clears the previous frame and sets _nextFrame to null in order to prepare for the next frame.
  • If an exception occurs while decoding the next frame, an error is recorded and returned. If the listener is removed while waiting for the decoding of the next frame, the frame will not be emitted when there is no active listener, otherwise the frame will be emitted and the decoding of the next frame will be scheduled.

_emitFrameThe function of the method is toImageStreamCompleterSend a new oneImageInfo. The specific implementation is through callsetImageMethod willImageInfoSet asImageStreamCompleterThe current value of_framesEmittedcounter.

_codec!.getNextFrame()

_nextFrame = await _codec!.getNextFrame();
/// Fetches the next animation frame.
///
/// Wraps back to the first frame after returning the last frame.
///
/// The returned future can complete with an error if the decoding has failed.
///
/// The caller of this method is responsible for disposing the
/// [] on the returned object.
Future<FrameInfo> getNextFrame() async {
  final Completer<FrameInfo> completer = Completer<FrameInfo>.sync();
  final String? error = _getNextFrame((_Image? image, int durationMilliseconds) {
    if (image == null) {
      (Exception('Codec failed to produce an image, possibly due to invalid image data.'));
    } else {
      (FrameInfo._(
        image: Image._(image, , ),
        duration: Duration(milliseconds: durationMilliseconds),
      ));
    }
  });
  if (error != null) {
    throw Exception(error);
  }
  return ;
}
/// Returns an error message on failure, null on success.
String? _getNextFrame(void Function(_Image?, int) callback) native 'Codec_getNextFrame';

getNextFrame()yesCodecA method of the class to obtain the decoded frame. Specifically, it willCodecInternal decoded image frame, return aFrameInfoobject containing decodedImageThe object and the time stamp and duration of the frame. becauseCodecAnimated images may be supported, sogetNextFrame()Methods may return multiple frames.

existMultiFrameImageStreamCompletermiddle,_decodeNextFrameAndSchedule()The method will be called_codec.getNextFrame()Method to get the next frame of image and save it in_nextFramein the attribute. if_codecofframeCountThe property is 1, which means that this is a static image, and is used directly_emitFrame()Method publishes the image of the frame; otherwise, call_scheduleAppFrame()Method to schedule the release of the next frame.

_emitFrame (important method, notify the listener to trigger the callback and update the UI)

void _emitFrame(ImageInfo imageInfo) {
  setImage(imageInfo);
  _framesEmitted += 1;
}

This method is_decodeNextFrameAndScheduleis called to process the decoded next frame image. If the current frame is a non-animated image, it will be called directlysetImageMethod updateImageStreamCompleterIf it is an animation image, it will plan the display of the next frame and wait for the decoding of the next frame.

_scheduleAppFrame

void _scheduleAppFrame() {
  if (_frameCallbackScheduled) {
    return;
  }
  _frameCallbackScheduled = true;
  (_handleAppFrame);
}

function_scheduleAppFrame()The function is to schedule a Flutter engine frame callback, which will be called in the callback._handleAppFrame()function.

Specifically, the implementation of this function includes the following steps:

1. Check_frameCallbackScheduledFlag, if true, it means that the frame callback has been scheduled and will be returned directly.

2._frameCallbackScheduledThe flag is set to true to indicate that the frame callback has been scheduled.

3. Call()Function, register a frame callback to the Flutter engine. The callback function is_handleAppFrame()

4. In_handleAppFrame()In the function, the next frame should be calculated based on the frame rate and number of frames played by the current animation, and then called again_decodeNextFrameAndSchedule()function to get and display the next frame of image. This completes a loop of animation playback.

_handleAppFrame

void _handleAppFrame(Duration timestamp) {
  _frameCallbackScheduled = false;
  if (!hasListeners) {
    return;
  }
  assert(_nextFrame != null);
  if (_isFirstFrame() || _hasFrameDurationPassed(timestamp)) {
    _emitFrame(ImageInfo(
      image: _nextFrame!.(),
      scale: _scale,
      debugLabel: debugLabel,
    ));
    _shownTimestamp = timestamp;
    _frameDuration = _nextFrame!.duration;
    _nextFrame!.();
    _nextFrame = null;
    final int completedCycles = _framesEmitted ~/ _codec!.frameCount;
    if (_codec!.repetitionCount == -1 || completedCycles <= _codec!.repetitionCount) {
      _decodeNextFrameAndSchedule();
    }
    return;
  }
  final Duration delay = _frameDuration! - (timestamp - _shownTimestamp);
  _timer = Timer(delay * timeDilation, () {
    _scheduleAppFrame();
  });
}

function_handleAppFrameIt is the core function of MultiFrameImageStreamCompleter, used to process the logic of multi-frame images. The following is a detailed interpretation of this function:

  • 1、_frameCallbackScheduled = false;

    • Will_frameCallbackScheduledSet to false to indicate that the next frame has not been scheduled yet.
  • 2、 if (!hasListeners) { return; }

    • If there is no listener, it will return directly.
  • 3、 assert(_nextFrame != null);

    • assertion_nextFrameNot empty.
  • 4、 _isFirstFrame() || _hasFrameDurationPassed(timestamp)

    • If it is the first frame or the frame time has exceeded_frameDuration, then do the following:
  • 5、 _emitFrame(ImageInfo(image: _nextFrame!.(), scale: _scale, debugLabel: debugLabel));

    • issueImageInfoEvents will_nextFrameThe image information of the data is passed in as parameters.
  • 6、 _shownTimestamp = timestamp;

    • renew_shownTimestampis the current timestamp.
  • 7、 _frameDuration = _nextFrame!.duration;

    • renew_frameDurationfor_nextFrameframe interval time.
  • 8、 _nextFrame!.(); _nextFrame = null;

    • release_nextFrameImage resources and_nextFrameSet to null.
  • 9、 final int completedCycles = _framesEmitted ~/ _codec!.frameCount;

    • Calculate the number of cycles that have been completed.
  • 10、 _codec!.repetitionCount == -1 || completedCycles <= _codec!.repetitionCount

    • If the number of loops is -1 (represents infinite loops) or the number of loops that have been completed is less than or equal to_codecThe number of cycles is performed:
  • 11、 _decodeNextFrameAndSchedule();

    • Decode the next frame and schedule the drawing of the next frame.
  • 12、 final Duration delay = _frameDuration! - (timestamp - _shownTimestamp);

    • Calculate the time required for the next frame to delay.
  • 13、_timer = Timer(delay * timeDilation, () { _scheduleAppFrame(); });

    • Use a timer to achieve delayed drawing of the next frame. The delay time isdelayMultiply bytimeDilation(can be called bytimeDilation = xto change the speed of time passing). When the timer is triggered, it will be called_scheduleAppFrameTo schedule the drawing of the next frame.

addListener

void addListener(ImageStreamListener listener) {
  if (!hasListeners &amp;&amp; _codec != null &amp;&amp; (_currentImage == null || _codec!.frameCount &gt; 1)) {
    _decodeNextFrameAndSchedule();
  }
  (listener);
}

This method isImageStreamCompleterclass methods, used toImageStreamCompleterAdd a listener. When the first listener is added toImageStreamCompleterWhen on, it will be checked_codecWhether it is null, if it is not null and there are multiple frame images or the current image is null, it will be called_decodeNextFrameAndSchedule()The method begins decoding the next frame of image and planning rendering. This is done to ensure that the first listener is added toImageStreamCompleterWhen the next frame of image is on, it starts decoding the next frame and notifies all listeners after the next frame rendering is completed. if_codecnull or the current image is a single frame image, it will not be called_decodeNextFrameAndSchedule()method. In this method, the(listener)Add the listener to the listener list.

removeListener

void removeListener(ImageStreamListener listener) {
  (listener);
  if (!hasListeners) {
    _timer?.cancel();
    _timer = null;
  }
}

removeListenerMethods are used toMultiFrameImageStreamCompleterRemove the givenImageStreamListener. When removed, if the object no longer has any listeners, the timer will be cancelled_timer

Specifically, the method will call the parent class firstremoveListenerMethod, remove the listener from the listener list. Then, if at this timehasListenersforfalse, it means that there is no listener and will be cancelled_timertimer to free up resources.

_maybeDispose

void _maybeDispose() {
  super._maybeDispose();
  if (_disposed) {
    _chunkSubscription?.onData(null);
    _chunkSubscription?.cancel();
    _chunkSubscription = null;
  }
}

_maybeDispose()is a method used to free resources, called when the image stream is no longer listened to. It first calls the parent class's_maybeDispose()Method to handle some logic in the parent class to release resources. Then it checks_disposedWhether the property is true, if so, cancel the co-empty_chunkSubscription, This object is used to subscribe to the stream of image data blocks. This is done to free up related resources to prevent memory leaks.

Summarize

MultiFrameImageStreamCompleter is a class in Flutter that processes multi-frame pictures. It is mainly used to render each frame of a multi-frame animation picture to the screen.

This class mainly maintains a Codec object for decoding images, and also has an ImageInfo object for storing the information of the current frame. This class also implements the ImageStreamCompleter class, which can be used as the ImageStream of the Image object.

During the initialization of MultiFrameImageStreamCompleter, a Codec object is created and processed after the object is ready. When adding a listener, if the class currently has no listener and has obtained the first frame of image, the class will decode and render subsequent frames. If the class is destroyed, the Codec object is cleared.

In the main methods of this class, the _handleCodecReady() method will be called at initialization, which is used to set the decoded Codec object and start decoding and rendering the next frame of the picture with a listener.

The _decodeNextFrameAndSchedule() method is used to decode and render the next frame image. The next frame image is obtained through the _codec!.getNextFrame() method and render it. If there is only one frame of image at present, the frame image will be directly rendered and stopped.

The _handleAppFrame() method is used to process the logic of rendering the next frame of picture. It calculates the rendering time of the next frame of picture based on the timestamp, sets a delay timer, and calls this method regularly.

The addListener() and removeListener() methods are used to add and remove listeners and stop decoding and rendering when there is no listener.

Finally, the _maybeDispose() method is called when the class is destroyed and is used to clear the internal cache.

Reference link

In-depth image loading process

The above is the detailed analysis of the MultiFrameImageStreamCompleter process for Flutter loading. For more information about Flutter MultiFrameImageStreamCompleter, please follow my other related articles!