Flutter-Engine-architecture

The-Engine-architecture

(译)
原文: https://github.com/flutter/flutter/wiki/The-Engine-architecture

Flutter 结合了一个 Dart 框架和一个高性能的引擎。

Flutter 引擎是一个用于运行高品质移动应用的可移植运行时。它实现了 Flutter 的核心库,动画和图形,文件和网络的 I/O,支持可访问性(accessibility),插件架构,以及用于开发,编译和运行 Flutter 应用程序的 Dart 运行时和开发工具。

EN
Flutter combines a Dart framework with a high-performance engine.
The Flutter Engine is a portable runtime for high-quality mobile applications. It implements Flutter's core libraries, including animation and graphics, file and network I/O, accessibility support, plugin architecture, and a Dart runtime and toolchain for developing, compiling, and running Flutter applications.

在这里插入图片描述
Flutter 引擎采用的核心技术有:Skia 是一个 2D 的图形渲染库,Dart,一个具有垃圾回收和面向对象语言的虚拟机,并将它们托管在一个壳(shell)中。不同的平台有不同的壳,比如,我们有 Android 和 iOS 的 壳(shell)。我们同样提供了嵌入式 API,这些 API 可以把 Flutter 的引擎作为一个库嵌入到其他环境(见《自定义 Flutter 引擎嵌入环境》)。

壳实现了特定平台的代码,比如:和 IME(屏幕上的键盘)通信,和系统的应用生命周期事件。

Dart 虚拟机实现了基础的 Dart 核心库,另外还有一个叫 dart:ui 的库,这个库提供了对 Skia 和壳底层的访问接口。壳可以绕过引擎,通过平台通道(Platform Channels)直接与 Dart 代码直通信。

EN
Flutter's engine takes core technologies, Skia, a 2D graphics rendering library, and Dart, a VM for a garbage-collected object-oriented language, and hosts them in a shell. Different platforms have different shells, for example we have shells for Android and iOS. We also have an embedder API which allows Flutter's engine to be used as a library (see Custom Flutter Engine Embedders).
The shells implement platform-specific code such as communicating with IMEs (on-screen keyboards) and the system's application lifecycle events.
The Dart VM implements the normal Dart core libraries, plus an additional library called dart:ui to provide low-level access to Skia features and the shell. The shells can also communicate directly to Dart code via Platform Channels which bypass the engine.

Threading

flutter engine并不负责创建和管理他们的线程, 它是有embeder(对应的platform android/iOS/widnow/Mac)系统创建,嵌入程序为其管理的线程提供flutter引擎taskRunners, 此外dartVM拥有自己独立的线程池. 不论是 Flutter 引擎还是嵌入环境都无法访问 Dart 虚拟机线程池中的线程。

EN
The Flutter engine does not create or manage its own threads. Instead, it is the responsibility of the embedder to create and manage threads (and their message loops) for the Flutter engine. The embedder gives the Flutter engine task runners for the threads it manages. In addition to the threads managed by the embedder for the engine, the Dart VM also has its own thread pool. Neither the Flutter engine or the embedder have any access to the threads in this pool.

TaskRunners

Flutter 引擎需要嵌入环境给4个 task runner 提供引用。Flutter 引擎并不关心这些引用是不是属于同一个 task runner,或者同一个线程是否在为多个 task runner 服务。为了获得最好性能,嵌入环境应该给每一个 task runner 创建一个专用的线程。虽然 Flutter 引擎并不关心线程在为哪一个 task runner 服务,但是 Flutter 引擎确实期望线程配置在 Flutter 引擎的整个生命周期内保持稳定。嵌入环境应该只在一个线程上执行对应 task runner 的任务(直到 Flutter 引擎被销毁)。

EN
The Flutter engine requires the embedder to give it references to 4 task runners. The engine does not care if the references are to the same task runner, or, if multiple task runners are serviced on the same thread. For optimum performance, the embedder should create a dedicated thread per task runner. Though the engine does not care about the threads the task runners are serviced on, it does expect that the threading configuration remain stable for the entire lifetime of the engine. That is, once the embedder decides to service a task runner on a particular thread, it should execute tasks for that task runner only on that one thread (till the engine is torn down).

The main task runners are:

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

Platform Task Runner

嵌入环境线程的 task runner, 可以认为是主线程的 task runner。这个通常是 Android 的主线程 或者在 Apple 平台上的 Foundation 引用的线程。

分配给 task runner 对应线程的不论什么优先级的任务,这些任务的分配都是由嵌入环境来决定的。Flutter 引擎没有给这个线程赋予任何特殊的意义。事实上,可以使用基于不同线程的 Platform Task Runner 来启动多个 Flutter 引擎。这就是 Fuchsia 操作系统中 Flutter Content Handler 的工作原理。在每一个 Flutter 应用实例创建的过程中就会对应的创建一个 Flutter 引擎的实例,相应的也会为每一个 Flutter 引擎的实例创建对应的平台线程。

不论以什么方式和 Flutter 引擎交互,都必须在平台的线程上进行。在其他线程上和引擎交互在未优化版本中会跳过断言,并且这在发布版本中是线程不安全的。Flutter 引擎中许多组件都是线程不安全的。一旦 Flutter 引擎设置好并开始运行,只要对 Flutter 引擎的嵌入 API 都是在平台线程上进行访问的,嵌入环境就不需要发布任务到任何 task runner 去配置 Flutter 引擎。

除了作为嵌入环境启动之后与 Flutter 引擎进行交互的线程之外,task runner 还要执行正在等待的平台消息。这是非常方便的,因为访问 Platform 上的大多数 API 只有在 Platform 的主线程上是安全的。插件就不需要把自己的调用重新穿入(rethread)到主线程上。如果插件管理自己的工作线程,那么插件就要负责将响应队列返回给平台线程,然后才能把响应提交给引擎上的 Dart 代码去处理。始终在平台线程上与引擎交互的规则在这里得到保证。

EN
   This is the task runner for the thread the embedder considers as its main thread. For example, this is typically the Android Main Thread or the Main Thread referenced by Foundation on Apple platforms.
Any significance assigned to the thread for this task runner is entirely assigned by the embedder. The Flutter engine assigns no special meaning to this thread. In fact, multiple Flutter engines can be launched with platform task runners based on different threads. This is how the Flutter Content Handler in Fuchsia works. A new Flutter engine is created in the process for each Flutter application and a new platform thread is created for each engine.
Interacting with the Flutter engine in any way must happen on the platform thread. Interacting with the engine on any other thread will trip assertions in unoptimized builds and is not thread safe in release builds. There are numerous components in the Flutter engine that are not thread safe. Once the Flutter engine is setup and running, the embedder does not have to post tasks to any of the task runners used to configure the engine as long as all accesses to the embedder API are made on the platform thread.
In addition to being the thread on which the embedder interacts with the engine after it is launched, this task runner also executes any pending platform messages. This is handy because accessing most platform APIs is only safe on the platform’s main thread. Plugins don’t have to rethread their calls to the main thread. If plugins manage their own worker threads, it is their responsibility to queue responses back onto the platform thread before they can be submitted back to the engine for processing by Dart code. The rule of always interacting with the engine on the platform thread holds here.
Even though blocking the platform thread for inordinate amounts of time will not block the Flutter rendering pipeline, platforms do impose restrictions on expensive operations on this thread. So it is advised that any expensive work in response to platform messages be performed on separate worker threads (unrelated to the four threads discussed above) before having the responses queued back on the the platform thread for submission to the engine. Not doing so may result in platform-specific watchdogs terminating the application. Embeddings such as Android and iOS also uses the platform thread to pipe through user input events. A blocked platform thread can also cause gestures to be dropped.

UI Task Runners

UI Task Runner是引擎隔离层(RootIsoLate)执行dart代码的地方, RootIsolate是一个特殊的隔离空间,实现了dart:ui和fluttter engine之间的绑定.

  • 执行main函数一下部分的代码,更新三棵树,Widget,Element,Render tree
  • RootIsolate同时也负责控制Flutter渲染的每一帧,根隔离(root isolate)必须告诉引擎需要渲染的每一帧。引擎会询问平台是不是在下一个 vsync 的时候通知 UI Task runner。
  • 平台会等待下一个 vsync - 在 vsync 中, 引擎会唤醒 Dart 代码并执行以下操作:
  • 更新动画插值器(interpolators)。
  • 在布局阶段重建应用程序中的 widget。布局新实例化的 widgets 并立即绘制图层树并提交给引擎,并没有实际的渲染到屏幕上(rasterized);这里只构造出在渲染阶段所需要渲染的描述
  • 构造或者更新包含语义信息的的 widgets 节点树,这将会用来更新特定平台的可访问(accessibility)组件。
  • 根隔离还要执行平台上插件消息的所有响应,定时器(timers),微任务(microtask)和异步 I/O (socket,文件句柄(handles)等)。

UI线程生成图层树,涂层树决定了引擎最终在屏幕上需要显示的内容,因此UI线程上的耗时操作将会导致flutter的卡顿,哪怕几毫秒也足以导致错过下一帧. 耗时操作通常是由dart代码造成的,如在build阶段定义了太多的业务逻辑,引擎中不会在UI Task Runner上调度任何的native代码任务. 因此UI线程也被称作为dart线程,平台的嵌入层可以发布任务到这个TaskRunner上,但这可能会导致Flutter应用的卡顿,建议不要这样操作,而是应该给这样的操作分配特定的线程(Platform线程)

然而在实际的开发中,我们不可能避免dart代码中不做耗时的操作,建议将这样的代码移动到独立的Dart isolate中,比如提供的coumpute方法. 如果Dart代码在非根隔离上执行,那么这段代码将会在Dart VM管理的线程池的线程中执行,这样就不会导致flutter应用的卡顿。 非RootIsolate中不能调用flutter框架的绑定的方法,因为它们的绑定是在RootIsolate上执行的.

GPU Task Runner

GPU Task Runner执行访问设备上的GPU的任务,执行在UI Task Runner上的Dart代码所创建的图层树,相同的图层树可以由 OpenGL,Vulkan 或者为 Skia 配置的其它渲染库来渲染帧。 GPU Task Runner使用图层树构造响应的GPU指令,GPU Task Runne还负责为特定帧设置所有的GPU资源,包括与平台通信的帧缓冲区,管理surface的生命周期,并确保完全准备特定帧的textture(GPU线程中一段连续的空间)和缓冲区。

根据处理图层树所需要的时间以及GPU完成显示帧所需要的时间, GPU Task Runner上各种组件可以延迟UI线程上的其它帧的调度,通常,UI和GPU任务运行程序位于不同线程上,这种情况下可能出现UI线程提交渲染的图层树到GPU时,GPU线程处于向GPU提交帧的过程中,在vsync周期中前后衔接不上. 由于GPU task runner 的组件可能会在 UI 线程上引起帧调度延迟,因此在 GPU 线程上执行太多工作将导致 Flutter 应用程序的卡顿。通常,用户没有机会在 GPU task runner 上执行自定义任务,因为平台代码和 Dart 代码都无法访问 GPU task runner。但是,嵌入器仍然可以在此线程上安排任务。因此,建议嵌入器为每个引擎实例的 GPU task runner 提供专用线程。

IO Task Runner

上述所提到的Task Runner都可以执行的操作类型有很强的限制,过长时间的阻塞都会带来程序的运行bug,如PlatformTaskRunner如果默认使用主线程创建,则可能会促发WatchDog, 阻塞UI或GPU Task runner将导致flutter应用程序卡顿。 GPU线程需要执行一些非常昂贵的耗时操作,这些昂贵的耗时操作是在IO Task Runner上执行的。

IO Task Runner主要功能是从asset store读取压缩图像,并确保这些图像已经准备好在GPU task runner上渲染,为了确保texture已经准备好渲染,首先必须将其作为压缩数据(通常为PNG,JPEG等)从 asset store 读取,解压缩为 GPU 友好格式并传递给 GPU。这些操作是比较费时的,如果在GPU上操作将会导致渲染时间过长,容易引起掉帧. 此外IO Task Runner会设置一组特殊的上下文,该上下文与主CPU

由于只有 GPU task runner 可以访问 GPU,因此 IO task runner 组件会设置一个特殊的上下文,该上下文与主 GPU task runner 上下文位于同一个共享组中。这在引擎设置的早期就会发生,也是 IO 任务有一个 task runner 的原因。实际上,压缩字节的读取和解压可以在线程池上进行。IO task runner 是比较特殊的,因为只能从特定线程访问上下文才是安全的操作。获取像 ui.Image 这样的资源的唯一方法是通过异步调用;允许框架与 IO task runner 通信,以便它可以异步执行所提到的所有 texture 操作。然后可以立即在帧中使用该图像,而 GPU 线程不必进行昂贵的操作。

用户代码无法通过 Dart 或原生插件访问此线程。甚至嵌入器也可以自由地在这个线程上调度相当昂贵的任务。这不会导致 Flutter 应用程序的卡顿,但可能会延迟未来对图片和其他资源的的及时的处理。即便如此,建议自定义嵌入器为 IO task runner 设置专用线程。

当前平台特定线程的配置
就像之前提到的,引擎支持多线程的配置,支持多线程配置的平台有:

iOS

为每一个引擎实例的 UI,GPU 和 IO task runner 创建专用线程。在同一平台上所有的引擎实例共享平台线程和 task runner。

Android

为每一个引擎实例的 UI,GPU 和 IO task runner 创建专用线程。在同一平台上所有的引擎实例共享平台线程和 task runner。

Fuchsia

为每一个引擎实例的 UI,GPU 和 IO task runner 创建专用线程。

Flutter Tester

进程中单实例引擎的 UI, GPU, IO 和 平台的 task runner 使用相同的主线程。

文本渲染

我们的文本渲染过程如下:

libtxt: 字体选择, bidi, 断行(line breaking)。
HarfBuzz: 字形(glyph)选择, shaping。
Skia: (渲染/GPU 后台), 它在Android和Fuchsia上使用FreeType进行字体渲染,在iOS上使用CoreGraphics进行字体渲染。

Support

https://mrale.ph/dartvm/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值