IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Flutter : Engine 架构 -> 正文阅读

[移动开发]Flutter : Engine 架构

写在前面

本篇主要是对 Flutter 引擎(Engine)进行概述,主要来自官方的 The Engine architecture

内容

简介

Flutter Engine 实现了 Flutter 的一系列核心库,包括动画和图像,文件还有网络 I/O,辅助功能支持,插件架构,还有用于开发、编译、运行 Flutter 应用的 Dart 运行时和工具链。

Flutter 的引擎把核心技术,Skia,2D 图像渲染库,还有 Dart,一门具有垃圾回收机制、面向对象语言的虚拟机放进一个壳(shell)里。不同的平台有不同的壳。例如现在有针对 Android 和 iOS 的壳。另外还有 embedder API 可以让 Flutter 的引擎当做库来使用。(参考 Custom Flutter Engine Embedders

线程

Flutter 引擎本身是没有创建和管理自己的线程的操作。相反,是 embedder 负责为 Flutter 引擎创建和管理线程。embedder 使用 task runners 的概念为 Flutter 引擎提供线程管理。此外 Dart 虚拟机也有它自己的线程池,但 Flutter 引擎和 embedder 都没有权限访问它。

Task Runner 配置

Flutter 引擎让 embedder 给它提供 4 个 Task Runner 的引用。对引擎来说,它不关心这些引用是否实际上就是同一个 task runner,或者这几个 task runner 是同一个线程来服务的 (相当于说 Flutter 引擎需要四个这样的接口,至于接口的具体实现它是不关心的)。但从最佳性能上来说,embedder 应该是要为每个 task runner 创建单独的线程。毕竟虽然引擎不关注这些,但它还是期望线程的配置在整个生命周期里应该是稳定的。

也就是说一旦 embedder 决定选择一个特定的线程来服务一个 task runner,那它就应该只是为这个 task runner 执行任务。

这些主要的 task runner 分别是:

  • Platform Task Runner
  • UI Task Runner
  • GPU Task Runner
  • IO Task Runner

Platform Task Runner

embedder 把这个 task runner 所对应的线程当做主线程。例如 Android 里的 Main Thread 或者是苹果平台里的 Main Thread。

这个 task runner 所对应的线程的任何优先级的任务都是由 embedder 赋予的。 Flutter 引擎对这个线程来说没有什么特别的含义。也就是说我们可以做到让多个 Futter 引擎运行在不同线程的 Platform Task Runner 上。这就是在 Fuchsia 系统里,Flutter Content Handler 的工作方式:每一个 Flutter 应用都会创建出一个新的 Flutter 引擎,并且每个引擎也相应的会创建出一个平台线程 (在 iOS 和 Android 上,则是多个 Flutter 引擎实例是共享同一个 Platform Task Runner )

只要是跟 Flutter 引擎发生交互,就必须在平台线程里进行。如果是在其它线程里跟平台进行交互,那么在未优化的版本里,那些断言就会失效(trip assertions),还有在 release 版本里,无法起到线程安全的效果。Flutter 引擎里很多组件都不是线程安全的。一旦 Flutter 引擎配置好后并开始运行,所有 embedder API 的访问就都在平台线程里进行。

另外,这个线程除了处理 embedder 和引擎的交互外,还需要执行任何平台的消息。因为只有在平台的主线程里访问大部分的平台 API 才是安全的。对于插件,它不需要把调用自己的线程切换(rethread)到主线程里。如果插件管理自己的工作线程,那么它需要负责将返回结果派发回 Platform Thread 以便 Dart 能够安全地处理。总的来说,就是要让与引擎的交互始终都在平台线程里,这是规定。

虽然说长时间阻塞平台线程不会阻塞 Flutter 的渲染管道(rendering pipeline),但平台还是会对这个线程里这些耗费很大的操作进行限制。所以还是建议说在把返回结果提交给平台线程到引擎之前,把这些执行响应平台消息的耗费很大的工作放在单独的工作线程(不包括上面提到的四个线程)里去处理。如果不这样做的话,可能会导致一些平台上的 watchdog 终止程序。就像在 Android 或是 iOS 上,它们使用平台线程来处理用户的输入,如果你阻塞了这个平台线程,那就会导致手势处理失效。

UI Task Runner

Flutter 引擎为 root isolate 执行的所有 Dart 代码就在 UI Task Runner 里。root isolate 是一个特殊的 isolate,它绑定了 Futter 需要的函数。这个 isolate 运行应用的主要 Dart 代码。在这个 isolate 上的绑定(Bindings)操作使得引擎具有调度和提交帧的能力。(具体应该是指 WidgetsBinding mixin 上的内容)

对于每一个需要 Flutter 去渲染的帧来说:

  • 首先 root isolate 需要告诉引擎这个帧需要被渲染 (例如 setState
  • 然后引擎会告诉平台在下一个垂直信号到来的时候通知它 (注册监听回调SchedulerPhase.persistentCallback
  • 接着平台就等待下一个垂直信号的到来
  • 在垂直信号里,引擎会调用 Dart 代码来执行以下操作(perform the following):
    • 更新动画插值
    • 在 build 阶段重建应用里的 widget
    • 将新创建的对象和 widget 绘制到树里,然后提交给引擎。此时还没有进行光栅化处理。只有一些用于在绘制阶段里应该有什么需要被描绘的信息而已。
    • 然后创建或是更新树里那些节点里那些在屏幕上有包含语音信息的 widget。这主要是用来更新特定平台里的辅助功能组件。

这部分就是 WidgetsBinding 里的 drawFrame方法

  /// binding.dart
 
  /// Pump the build and rendering pipeline to generate a frame.
  ///
  /// This method is called by [handleDrawFrame], which itself is called
  /// automatically by the engine when it is time to lay out and paint a
  /// frame.
  ///
  /// Each frame consists of the following phases:
  ///
  /// 1. The animation phase: The [handleBeginFrame] method, which is registered
  /// with [PlatformDispatcher.onBeginFrame], invokes all the transient frame
  /// callbacks registered with [scheduleFrameCallback], in registration order.
  /// This includes all the [Ticker] instances that are driving
  /// [AnimationController] objects, which means all of the active [Animation]
  /// objects tick at this point.
  ///
  /// 2. Microtasks: After [handleBeginFrame] returns, any microtasks that got
  /// scheduled by transient frame callbacks get to run. This typically includes
  /// callbacks for futures from [Ticker]s and [AnimationController]s that
  /// completed this frame.
  ///
  /// After [handleBeginFrame], [handleDrawFrame], which is registered with
  /// [PlatformDispatcher.onDrawFrame], is called, which invokes all the
  /// persistent frame callbacks, of which the most notable is this method,
  /// [drawFrame], which proceeds as follows:
  ///
  /// 3. The build phase: All the dirty [Element]s in the widget tree are
  /// rebuilt (see [State.build]). See [State.setState] for further details on
  /// marking a widget dirty for building. See [BuildOwner] for more information
  /// on this step.
  ///
  /// 4. The layout phase: All the dirty [RenderObject]s in the system are laid
  /// out (see [RenderObject.performLayout]). See [RenderObject.markNeedsLayout]
  /// for further details on marking an object dirty for layout.
  ///
  /// 5. The compositing bits phase: The compositing bits on any dirty
  /// [RenderObject] objects are updated. See
  /// [RenderObject.markNeedsCompositingBitsUpdate].
  ///
  /// 6. The paint phase: All the dirty [RenderObject]s in the system are
  /// repainted (see [RenderObject.paint]). This generates the [Layer] tree. See
  /// [RenderObject.markNeedsPaint] for further details on marking an object
  /// dirty for paint.
  ///
  /// 7. The compositing phase: The layer tree is turned into a [Scene] and
  /// sent to the GPU.
  ///
  /// 8. The semantics phase: All the dirty [RenderObject]s in the system have
  /// their semantics updated (see [RenderObject.assembleSemanticsNode]). This
  /// generates the [SemanticsNode] tree. See
  /// [RenderObject.markNeedsSemanticsUpdate] for further details on marking an
  /// object dirty for semantics.
  ///
  /// For more details on steps 4-8, see [PipelineOwner].
  ///
  /// 9. The finalization phase in the widgets layer: The widgets tree is
  /// finalized. This causes [State.dispose] to be invoked on any objects that
  /// were removed from the widgets tree this frame. See
  /// [BuildOwner.finalizeTree] for more details.
  ///
  /// 10. The finalization phase in the scheduler layer: After [drawFrame]
  /// returns, [handleDrawFrame] then invokes post-frame callbacks (registered
  /// with [addPostFrameCallback]).
  //
  // When editing the above, also update rendering/binding.dart's copy.
  @override
  void drawFrame() {
    assert(!debugBuildingDirtyElements);
    assert(() {
      debugBuildingDirtyElements = true;
      return true;
    }());

    TimingsCallback? firstFrameCallback;
    if (_needToReportFirstFrame) {
      assert(!_firstFrameCompleter.isCompleted);

      firstFrameCallback = (List<FrameTiming> timings) {
        assert(sendFramesToEngine);
        if (!kReleaseMode) {
          developer.Timeline.instantSync('Rasterized first useful frame');
          developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});
        }
        SchedulerBinding.instance!.removeTimingsCallback(firstFrameCallback!);
        firstFrameCallback = null;
        _firstFrameCompleter.complete();
      };
      // Callback is only invoked when FlutterView.render is called. When
      // sendFramesToEngine is set to false during the frame, it will not be
      // called and we need to remove the callback (see below).
      SchedulerBinding.instance!.addTimingsCallback(firstFrameCallback!);
    }

除了为引擎的最终渲染构建帧之外,root isolate 还需要处理来自平台插件的信息、计时器、微任务和异步 I/O (来自 socket ,文件句柄等)的响应。

就像上面说的, UI 线程构建了一棵引擎最终在屏幕里实际上应该画什么的树,这说明了它是屏幕里所有内容的源头。所以如果在这个线程上执行长时间的同步操作,那么就会让 Flutter 应用发生卡顿(jank)(就算只有几毫秒,也足以丢失下一帧!)。这种耗时的操作是由 Dart 代码引起的,因为引擎不会在这个 task runner 上调用任何原生代码来做这样的操作。因此,就可以把这个 task runner (或者说线程)认为是 Dart 线程。embedder 是有可能添加任务到这个 task runner 上的,因此就有可能导致在 Flutter 应用上会有卡顿的发生,所以还是建议 embedder 为这个 task runner 分配一个指定的线程。

如果我们实在无法避免 Dart 代码执行耗时的操作,那我们应该把这部分的代码放到一个独立的 Dart 的 isolate 里(例如使用 compute 方法)。这样这些 Dart 代码就会执行在一个非 root 的 isolate 里,这个非 root 的 isolate 所在的线程则是来自 Dart 虚拟机管理的线程池上。这样 Flutter 应用就不会发生 卡顿现象。终止 root isolate 会终止所有由这个 root isolate 所派生(spwaned)出来的 isolate。另外要注意的,非 root isolate 是不会有帧调度,并且也不会有跟 Flutter Framework 的绑定。也正因为这样,你没有办法使用任何手段让这种非 root isolate 跟 Flutter Framework 进行交互。它的目的就是用来做一些大量计算的任务。

Raster Task Runner

Raster Task Runner 也就是 GPU Task Runner。Raster 就是光栅化的意思,因此这个 task runner 就是用来执行设备上那些需要光栅化(通常是 GPU 来处理)的任务。在 UI task runner 上的 Dart 代码所创建的图层树(layer tree)与客户端渲染API是不相关的。也就是说,同样的图层树想要渲染出一帧,可以使用 OpenGL,Vulkan,或者为 Skia 所配置的其它渲染库来实现。GPU Task Runner 上的组件会使用图层树来构建合适的绘制命令,并且还会为特定的帧设置所有的 GPU 资源。这包括让平台设置 framebuffer,管理 surface 生命周期,确保每个帧所需要的 textures 和 buffers 都已完全准备好。

根据层级树和设备显示完一帧所需要的时间,Raster Task Runner 上的不同组件可能会延迟 UI 线程上的下一帧的调度(即有先后顺序)。为什么要这样做呢?因为通常,UI 和 Raster Task Runner 是在不同的线程上运行。在这样的前提下,就会出现这样的情况,UI 线程已经为下一帧做好了准备,而光栅化线程却还在提交帧给 GPU。因此使用这种延迟调度机制可以让 UI 线程不会安排太多的工作给光栅化的操作。

正如上面说的, Raster Task Runner 的组件会让 UI 线程上产生帧的延迟调度,所以在 Raster Task Runner 上执行太多的工作也会导致 Flutter 应用的卡顿发生。但通常我们也没有机会说在这个 task runner 上去自定义一些执行任务,因为不管是平台代码还是 Dart 代码都没有权限去访问这个 task runner。但还是有一点可能的,就是让 embedder 在这个线程上做一些任务调度的操作。比方说可以让 embedder 为每个引擎实例的 Raster Task Runner 提供一个特定的线程。

IO Task Runner

上面所提到的那些 task runner 都在它们所对应的一些操作类型上有很严格的限制:长时间阻塞 Platform Task Runner 会触发平台的 watchdog,阻塞 UI Task Runner 或是 Raster Task Runner 会导致 Flutter 应用里产生卡顿。然而,还有一些对于光栅线程(raster thread)来说很必要同时也很耗时的工作,是在 IO Task Runner 上来做的。

IO Task Runner 的主要作用是从 asset 里读取压缩图片,然后确保这些图片已经准备好给 Raster Task Runner 进行渲染。为了确保一个 texture 已经做好了渲染准备,它首先需要从 asset 里读取压缩数据(通常是 PNG、JPEG 等),然后解压缩成 GPU 可处理的格式再传给 GPU。这部分的操作在 Raster Task Runner 上处理的话会导致卡顿。但因为只有 Raster Task Runner 可以访问到 GPU,IO Task Runner 的组件会设置一个特殊的 context,这个 context 跟 Raster Task Runner 的 context 是在同一个共享组(sharegroup)里的。这个时机在引擎设置的时候就做了,这也是为什么需要一个单独的 task runner 来执行 IO 任务的原因。实际上,读取压缩数据和解压是可以在一个线程池里进行的。IO Task Runner 这么特殊就是因为只有通过一个特定的线程访问 context 才是线程安全的。像从 ui.Image里获取资源的唯一方法就是异步调用,这样就能够让 Framework 告诉 IO Task Runner,叫它可以用异步的方式来执行所有前面所提到的所有 texture 的操作。这样图片就可以立即被用到帧上,而不需要 Raster Task Runner 做这部分的耗时工作。

同样的,Dart 代码或是原生插件也没有权限访问这个线程。对 embedder,它可以安排一些非常耗时的任务到这个线程上。这样做不会让 Flutter 应用发生卡顿,但可能对接下来的图片还有其它资源的处理产生延迟处理的情况。因此,建议那些自定义的 embedder 为这个 task runner 同样设置一个特定的线程来处理。

当前平台特定线程的配置

像前面提到的,引擎可以提供有不同的线程配置,目前不同平台对此的支持如下:

iOS 与 Android

在每一个引擎实例里,UI、栅格和 IO 的 task runner 会有相应的一个专门创建的线程来处理。所有的引擎实例共享同一个平台线程和 task runner。

Fuchsia

每一个引擎实例里的 UI、栅格、IO 和 平台 task runner 都有相应的一个专门线程来处理。

小结

官方的这一篇介绍里,一方面是对 Flutter 引擎的线程模型做了概述,另一方面也为 Embedder 在 Task Runner 上的设计提供了指导意见。

参考

The Engine architecture

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-08-10 13:32:03  更:2021-08-10 13:33:40 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 0:07:36-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码