Flutter加载图片流程MultiFrameImageStreamCompleter解析

目录
  • MultiFrameImageStreamCompleter
  • _handleCodecReady
  • _decodeNextFrameAndSchedule
  • _codec!.getNextFrame()
  • _emitFrame(重要方法, 通知监听器触发回调,更新UI)
  • _scheduleAppFrame
  • _handleAppFrame
  • addListener
  • removeListener
  • _maybeDispose
  • 总结

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,这个对象是用来订阅图像数据块的流。这样做是为了释放相关的资源,以防止内存泄漏。

总结

MultiFrameImageStreamCompleter 是 Flutter 中用于处理多帧图片的类,主要用于将多帧动画图片的每一帧渲染到屏幕上。

该类内部主要维护了一个 Codec 对象,用于解码图片,同时也有一个 ImageInfo 对象用于存储当前帧的信息,并且该类也实现了 ImageStreamCompleter 类,可以作为 Image 对象的 ImageStream。

在 MultiFrameImageStreamCompleter 的初始化过程中,会创建 Codec 对象,并且在该对象准备好后进行处理,并且在添加监听器时,如果该类当前没有监听器,并且已经获取了第一帧图像,那么该类会进行后续帧的解码和渲染。如果该类被销毁,则会清空 Codec 对象。

在该类的主要方法中,_handleCodecReady() 方法会在初始化时进行调用,用于设置解码后的 Codec 对象,并在有监听器的情况下开始解码和渲染下一帧图片。

_decodeNextFrameAndSchedule() 方法用于解码和渲染下一帧图片,通过 _codec!.getNextFrame() 方法获取下一帧图像,并进行渲染处理,如果当前只有一帧图片,则直接渲染该帧图片并停止。

_handleAppFrame() 方法用于处理渲染下一帧图片的逻辑,会根据时间戳计算出下一帧图片的渲染时间,并设置延时定时器,定时调用该方法。

addListener() 和 removeListener() 方法用于添加和删除监听器,并在没有监听器时停止解码和渲染。

最后,_maybeDispose() 方法会在该类被销毁时进行调用,用于清空内部缓存。

参考链接

深入图片加载流程

以上就是Flutter加载图片流程MultiFrameImageStreamCompleter解析的详细内容,更多关于Flutter MultiFrameImageStreamCompleter的资料请关注我们其它相关文章!

(0)

相关推荐

  • Flutter图片缓存管理ImageCache原理分析

    目录 引言 PaintingBinding 减少图片缓存 增大阀值 思考 引言 设计: 嗯? 这个图片点击跳转进详情再返回图片怎么变白闪一下呢?产品: 是啊是啊! 一定是个bug开发: 囧囧囧 在开发过程中, 也许你也遇到过这样一个场景. 进入一个页面后,前一个页面的图片都会闪白一下. 或者在列表中,加载很多列表项后,之前列表中的图片都需要重新加载.你有没有想过这一切的原因是什么呢? 没错! 它就是我们今天介绍的主人公 --- ImageCache 可能有些人对ImageCache还有些陌生,

  • Flutter加载图片流程之ImageProvider源码示例解析

    目录 加载网络图片 ImageProvider resolve obtainKey resolveStreamForKey loadBuffer load(被废弃) evict 总结 困惑解答 加载网络图片 Image.network()是Flutter提供的一种从网络上加载图片的方法,它可以从指定的URL加载图片,并在加载完成后将其显示在应用程序中.本节内容,我们从源码出发,探讨下图片的加载流程. ImageProvider ImageProvider是Flutter中一个抽象类,它定义了一种

  • Flutter图片与文件选择器使用实例

    目录 引言 一.image_picker 1.安装 2.使用 3.属性 4.注意 二.flutter_document_picker 1.安装 2.使用 总结 引言 我已经一个多星期没碰过电脑了,今日上班,打开电脑的第一件事就是想着写点什么.反正大家都还沉浸在节后的喜悦中,还没进入工作状态,与其浪费时间,不如做些更有意义的事情. 今天就跟大家简单分享一下Flutter开发过程中经常会用到的图片和文件选择器. 一.image_picker 一个适用于iOS和Android的Flutter插件,能够

  • Flutter 图片开发核心技能快速掌握教程

    目录 正文 使用网络图片 把网络图片缓存到磁盘 使用 assets 图片 适配浅色与深色模式 在不同的设备使用不同分辨率的图片 关于设备 dpr 不完全匹配的处理 忽略 dpr 信息 使用相册图片 使用相机拍摄的图片 使用内存图片 图片用做装饰 图片预加载 centerSlice centerSlice 只能放大,不能缩小. 全局缓存 ImageCache 的设置 图片类之间的关系 ImageProvider obtainKey(ImageConfiguration) 方法 resolve(Im

  • flutter中的资源和图片加载示例详解

    目录 封面图 指定相应的资源 资源绑定 Asset bundling 资源变体 加载资源 加载文本资源 加载图片 加载依赖包中的图片 最后 封面图 下个季度的目标是把前端监控相关的内容梳理出来,梳理出来之后可能会在公司内部做个分享- Flutter应用程序既括代码也包括一些其他的资产,我们通常这些资产为资源. 有时候我会思考assets这个单词,在程序中到底应该翻译为资产呢?还是翻译为资源?按照习惯,我们这里还是称为资源好了- 这些资源是一些与应用程序捆绑在一起和并且部署应用时会用到的的文件,在

  • flutter图片组件核心类源码解析

    目录 导语 问题 Image的核心类图及其关系 网络图片的加载过程 网络图片数据的回调和展示过程 补上图片内存缓存的源码分析 如何支持图片的磁盘缓存 总结 导语 在使用flutter 自带图片组件的过程中,大家有没有考虑过flutter是如何加载一张网络图片的? 以及对自带的图片组件我们可以做些什么优化? 问题 flutter 网络图片是怎么请求的? 图片请求成功后是这么展示的? gif的每一帧是怎么支持展示的? 如何支持图片的磁盘缓存? 接下来,让我们带着问题一起探究flutter 图片组件的

  • Flutter加载图片的多样玩法汇总

    目录 加载本地图片 圆角本地图片 效果图 代码 加载网络图片-本地图片占位图 加载网络图片-loading 效果 代码 圆角.边框.渐变 总结 加载本地图片 在项目目录下创建assets文件夹,再在其文件夹下创建images文件夹,后面将需要的图片复制到其中即可 在pubspec.yaml文件中添加引用 flutter: uses-material-design: true assets: - assets/images/ 在Container中加载本地图片 Container( width:

  • Flutter加载图片流程之ImageCache源码示例解析

    目录 ImageCache _pendingImages._cache._liveImages maximumSize.currentSize clear evict _touch _checkCacheSize _trackLiveImage putIfAbsent clearLiveImages 答疑解惑 ImageCache const int _kDefaultSize = 1000; const int _kDefaultSizeBytes = 100 << 20; // 100 M

  • 详解Flutter网络图片本地缓存的实现

    目录 一.问题 二.思路 三.实现 四.使用 五.缓存清理 一.问题 Flutter原有的图片缓存机制,是通过PaintingBinding.instance!.imageCache来管理缓存的,这个缓存缓存到的是内存中,每次重新打开APP或者缓存被清理都会再次进行网络请求,大图片加载慢不友好,且增加服务器负担. 二.思路 1.查看FadeInImage.assetNetwork.Image.network等几个网络请求的命名构造方法,初始化了ImageProvider. FadeInImage

  • Flutter加载图片流程MultiFrameImageStreamCompleter解析

    目录 MultiFrameImageStreamCompleter _handleCodecReady _decodeNextFrameAndSchedule _codec!.getNextFrame() _emitFrame(重要方法, 通知监听器触发回调,更新UI) _scheduleAppFrame _handleAppFrame addListener removeListener _maybeDispose 总结 MultiFrameImageStreamCompleter Multi

  • 解析spring加载bean流程的方法

    spring作为目前我们开发的基础框架,每天的开发工作基本和他形影不离,作为管理bean的最经典.优秀的框架,它的复杂程度往往令人望而却步.不过作为朝夕相处的框架,我们必须得明白一个问题就是spring是如何加载bean的,我们常在开发中使用的注解比如@Component.@AutoWired.@Socpe等注解,Spring是如何解析的,明白这些原理将有助于我们更深刻的理解spring.需要说明一点的是spring的源码非常精密.复杂,限于篇幅的关系,本篇博客不会细致的分析源码,会采取抽丝剥茧

  • JS图片懒加载技术实现过程解析

    懒加载技术 懒加载(LazyLoad)是前端优化的一种有效方式,极大的提升用户体验,图片一直是页面加载的流浪大户,现在一张图片几兆已经是很正常的事,远远大于代码的大小. 原理:页面加载后只让文档可视区内的图片显示,其它不显示,随着用户对页面的滚动,判断其区域位置,生成img标签,让到可视区的图片加载出来. 所用相关技术:给img加属性 (例如data-src),将图片的地址赋值给他,这样就生成img标签后再把data-src的值赋给img的src(通过dataset.src或者getAttrib

  • 如何在iOS中高效的加载图片详解

    目录 前言 图片的渲染流程 DataBuffer SD源码分析 ImageBuffer 占用内存大小 Xcode测试 如何减少图像占用内存 向下采样 SD源码分析解码过程 选择正确的图片渲染格式 渲染格式 如何正确的选择渲染格式 减少后备存储器的使用 减少或者不使用 draw(rect:) 方法 如何在列表中加载图片 线程爆炸 总结 前言 在iOS开发中,图片(UIImage)是我们在开发中,占用手机内存比较大的对象,如果在运行过程中,内存占用过大,对电池寿命会造成影响,如果超过了内存占用的最大

  • 网页资源阻塞浏览器加载的原理示例解析

    目录 正文 测试前环境准备 图片会造成阻塞吗? CSS 加载阻塞 CSS 会阻塞后面 JS 的执行吗? JS 加载阻塞 defer 和 async 动态脚本会造成阻塞吗? DOMContentLoaded 和 onload DOMContentLoaded 遇到脚本 DOMContentLoaded 遇到样式 正文 一个页面允许加载的外部资源有很多,常见的有脚本.样式.字体.图片和视频等,对于这些外部资源究竟是如何影响整个页面的加载和渲染的呢?今天来一探究竟. 如何用 Chrome 定制网络加载

  • 关于 jQuery Easyui异步加载tree的问题解析

    想要实现从本地中加载json文件,通过事件来动态的插入到ul中时,遇到了一小bug html中代码是这样的 <ul class="easyui-tree" id="tt"></ul> js中的代码 $(".next-menu:nth-child(1) a").click(function() { var $IDstr = $(this).attr("id"), $treeIDNum = parseInt

  • Jquery promise实现一张一张加载图片

    Promise是CommonJS的规范之一,拥有resolve.reject.done.fail.then等方法,能够帮助我们控制代码的流程,避免函数的多层嵌套.如今异步在web开发中越来越重要,对于开发人员来说,这种非线性执行的编程会让开发者觉得难以掌控,而Promise可以让我们更好地掌控代码的执行流程,jQuery等流行的js库都已经实现了这个对象,年底即将发布的ES6也将原生实现Promise. 在javascript设计模式实践之代理模式--图片预加载中用代理模式实现了图片预加载功能.

随机推荐