软件编程
位置:首页>> 软件编程>> flutter编程>> Flutter加载图片流程MultiFrameImageStreamCompleter解析

Flutter加载图片流程MultiFrameImageStreamCompleter解析

作者:Nicholas68  发布时间:2023-07-19 02:45:55 

标签:Flutter,MultiFrameImageStreamCompleter,图片加载流程

MultiFrameImageStreamCompleter

MultiFrameImageStreamCompleter 是一个可组合的 ImageStreamCompleter 类,用于将多个 ImageStreamCompleter 对象合并为一个单独的 ImageStream 对象,通常用于动画效果。每当子 ImageStreamCompleter 接收到一个新的 ImageInfo 对象,它会立即通知其所有的 * ,并将对象传递给它们。

MultiFrameImageStreamCompleteraddListener() 方法被调用时,它将传入的 ImageStreamListener 添加到其内部的子 ImageStreamCompleter 的 * 列表中。如果 MultiFrameImageStreamCompleter 本身接收到一个 ImageInfo 对象,它会将它传递给其所有的 * 。但是,它不会自己管理这些帧,而是委托给每个子 ImageStreamCompleter 来完成。

MultiFrameImageStreamCompleter 还支持渐进式 JPEG,并实现了 addListener()removeListener()dispose() 方法,以及一个名为 getNextFrame() 的方法,用于从图像流中获取下一帧。

当所有帧都加载完毕后,MultiFrameImageStreamCompleter 将使用 dart:ui.Codec 解码器将它们合并为一个单独的 dart:ui.Image 对象,并将其传递给 setImage() 方法。最后,它将通知所有 * ,并将它们传递给 ImageStreamListener.onImage() 回调函数,以通知它们新的 ImageInfo 已经可用。

MultiFrameImageStreamCompleterdispose() 方法被调用时,它会将其所有子 ImageStreamCompleterdispose() 方法依次调用,以释放所有资源,并取消所有未处理的帧请求。同时,它还会确保在释放资源之前将所有错误通知给其 * 。

_handleCodecReady

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

_handleCodecReady方法中,首先将传入的codec对象赋值给成员变量_codec,然后使用assert语句来确保该变量不为空。接着,如果当前对象有 * ,则调用_decodeNextFrameAndSchedule方法来解码下一帧并将其调度执行。这里的目的是为了尽早地开始解码下一帧图像,以尽快展示出完整的动画效果。如果没有 * ,则不需要解码下一帧图像,因为没有地方可以展示它。

_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?.image.dispose();
 _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!.image.clone(),
     scale: _scale,
     debugLabel: debugLabel,
   ));
   _nextFrame!.image.dispose();
   _nextFrame = null;
   return;
 }
 _scheduleAppFrame();
}
  • 这个方法的作用是获取下一帧并在获取成功后调度下一帧的解码,如果帧数为1,即这是一个静态图片,则只需返回该帧,并在没有 * 时直接返回,如果帧数大于1,则调度下一帧的解码。

  • 在获取下一帧之前,方法会清除上一帧并将_nextFrame置为null,以便准备下一帧。

  • 如果解码下一帧时发生异常,则会记录错误并返回。如果在等待下一帧的解码期间移除了 * ,则在没有活动的 * 时不会发出帧,否则会发出帧并调度下一帧的解码。

_emitFrame 方法的作用是向 ImageStreamCompleter 发送新的 ImageInfo。具体实现是通过调用 setImage 方法将 ImageInfo 设置为 ImageStreamCompleter 的当前值,同时更新 _framesEmitted 计数器。

_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
/// [FrameInfo.image] 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) {
     completer.completeError(Exception('Codec failed to produce an image, possibly due to invalid image data.'));
   } else {
     completer.complete(FrameInfo._(
       image: Image._(image, image.width, image.height),
       duration: Duration(milliseconds: durationMilliseconds),
     ));
   }
 });
 if (error != null) {
   throw Exception(error);
 }
 return completer.future;
}
/// Returns an error message on failure, null on success.
String? _getNextFrame(void Function(_Image?, int) callback) native 'Codec_getNextFrame';

getNextFrame()Codec 类的一个方法,用于获取解码后的帧。具体来说,它会在 Codec 内部解码图像帧,返回一个 FrameInfo 对象,其中包含了解码后的 Image 对象以及该帧的时间戳和持续时间等信息。由于 Codec 可能会支持动画图像,因此 getNextFrame() 方法可能会返回多个帧。

MultiFrameImageStreamCompleter 中,_decodeNextFrameAndSchedule() 方法会调用 _codec.getNextFrame() 方法获取下一帧图像,然后将其保存在 _nextFrame 属性中。如果 _codecframeCount 属性为 1,说明这是一个静态图像,直接使用 _emitFrame() 方法发布该帧图像;否则,调用 _scheduleAppFrame() 方法安排下一帧的发布。

_emitFrame(重要方法, 通知 * 触发回调,更新UI)

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

这个方法在 _decodeNextFrameAndSchedule 中被调用,用于处理已解码的下一帧图像。如果当前帧是非动画图像,它会直接调用 setImage 方法更新 ImageStreamCompleter 的值,如果是动画图像,它会计划下一帧的显示并等待下一帧的解码。

_scheduleAppFrame

void _scheduleAppFrame() {
 if (_frameCallbackScheduled) {
   return;
 }
 _frameCallbackScheduled = true;
 SchedulerBinding.instance.scheduleFrameCallback(_handleAppFrame);
}

函数 _scheduleAppFrame() 的作用是调度一个Flutter引擎帧回调,在回调中会调用 _handleAppFrame() 函数。

具体来说,这个函数的实现包含以下步骤:

1、检查 _frameCallbackScheduled 标志,如果为 true,则说明帧回调已经被调度过,直接返回。

2、将 _frameCallbackScheduled 标志设置为 true,表示帧回调已经被调度。

3、调用 SchedulerBinding.instance.scheduleFrameCallback() 函数,向Flutter引擎注册一个帧回调。回调函数为 _handleAppFrame()

4、在 _handleAppFrame() 函数中,将会根据当前动画播放的帧率和帧数,计算下一帧应该在何时被显示,然后再次调用 _decodeNextFrameAndSchedule() 函数,以获取并显示下一帧图像。这样就完成了一次动画播放的循环。

_handleAppFrame

void _handleAppFrame(Duration timestamp) {
 _frameCallbackScheduled = false;
 if (!hasListeners) {
   return;
 }
 assert(_nextFrame != null);
 if (_isFirstFrame() || _hasFrameDurationPassed(timestamp)) {
   _emitFrame(ImageInfo(
     image: _nextFrame!.image.clone(),
     scale: _scale,
     debugLabel: debugLabel,
   ));
   _shownTimestamp = timestamp;
   _frameDuration = _nextFrame!.duration;
   _nextFrame!.image.dispose();
   _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();
 });
}

函数 _handleAppFrame 是 MultiFrameImageStreamCompleter 的核心函数,用于处理多帧图像的逻辑。下面是对该函数的详细解读:

  • 1、_frameCallbackScheduled = false;

    • _frameCallbackScheduled 设为 false,表示下一帧还没有调度。

  • 2、 if (!hasListeners) { return; }

    • 如果没有 * ,则直接返回。

  • 3、 assert(_nextFrame != null);

    • 断言 _nextFrame 不为空。

  • 4、 _isFirstFrame() || _hasFrameDurationPassed(timestamp)

    • 如果是第一帧或者帧时间已经超过了 _frameDuration,则进行以下操作:

  • 5、 _emitFrame(ImageInfo(image: _nextFrame!.image.clone(), scale: _scale, debugLabel: debugLabel));

    • 发出 ImageInfo 事件,将 _nextFrame 的图像信息作为参数传入。

  • 6、 _shownTimestamp = timestamp;

    • 更新 _shownTimestamp 为当前时间戳。

  • 7、 _frameDuration = _nextFrame!.duration;

    • 更新 _frameDuration_nextFrame 的帧间隔时间。

  • 8、 _nextFrame!.image.dispose(); _nextFrame = null;

    • 释放 _nextFrame 的图像资源并将 _nextFrame 设为 null。

  • 9、 final int completedCycles = _framesEmitted ~/ _codec!.frameCount;

    • 计算已经完成的循环次数。

  • 10、 _codec!.repetitionCount == -1 || completedCycles <= _codec!.repetitionCount

    • 如果循环次数为 -1(表示无限循环)或者已经完成的循环次数小于等于 _codec 的循环次数,则进行以下操作:

  • 11、 _decodeNextFrameAndSchedule();

    • 解码下一帧并调度下一帧的绘制。

  • 12、 final Duration delay = _frameDuration! - (timestamp - _shownTimestamp);

    • 计算下一帧需要延迟的时间。

  • 13、_timer = Timer(delay * timeDilation, () { _scheduleAppFrame(); });

    • 使用计时器来实现下一帧的延迟绘制。延迟时间为 delay 乘以 timeDilation(可以通过调用 timeDilation = x 来改变时间流逝的速度)。当计时器触发时,将调用 _scheduleAppFrame 来调度下一帧的绘制。

addListener

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

这个方法是 ImageStreamCompleter 类的方法,用于向 ImageStreamCompleter 添加 * 。当第一个 * 被添加到 ImageStreamCompleter 上时,会检查 _codec 是否为 null,如果不为 null 并且有多帧图像或者当前图像为 null,则会调用 _decodeNextFrameAndSchedule() 方法开始解码下一帧图像并计划渲染。这样做是为了确保在第一个 * 被添加到 ImageStreamCompleter 上时就开始解码下一帧图像并在下一帧渲染完成后通知所有 * 。如果 _codec 为 null 或者当前图像为单帧图像,则不会调用 _decodeNextFrameAndSchedule() 方法。在这个方法中,调用了 super.addListener(listener) 将 * 添加到 * 列表中。

removeListener

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

removeListener 方法用于从 MultiFrameImageStreamCompleter 中移除给定的 ImageStreamListener。当移除后,如果该对象不再有任何 * ,就会取消定时器 _timer

具体来说,该方法会先调用父类的 removeListener 方法,将该 * 从 * 列表中移除。接着,如果此时 hasListenersfalse,说明没有任何 * ,就会取消 _timer 定时器,以便释放资源。

_maybeDispose

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

_maybeDispose()是一个用来释放资源的方法,当图片流不再被监听时调用。它首先调用父类的_maybeDispose()方法,以处理父类中的一些释放资源的逻辑。然后它会检查_disposed属性是否为true,如果是,则取消并置空_chunkSubscription,这个对象是用来订阅图像数据块的流。这样做是为了释放相关的资源,以防止内存泄漏。

来源:https://juejin.cn/post/7219868613666652217

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com