温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Flutter MultiFrameImageStreamCompleter是什么

发布时间:2023-04-20 11:32:04 来源:亿速云 阅读:122 作者:iii 栏目:开发技术

本篇内容主要讲解“Flutter MultiFrameImageStreamCompleter是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“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,这个对象是用来订阅图像数据块的流。这样做是为了释放相关的资源,以防止内存泄漏。

到此,相信大家对“Flutter MultiFrameImageStreamCompleter是什么”有了更深的了解,不妨来实际操作一番吧!这里是亿速云网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI