Flutter线程管理与Dart隔离机制
随着终端业务需求越来越复杂,版本迭代越来越频繁,需要多种集成跨平台开发的最优解决方案,以提高研发效率。目前,有 RN 和 Weex 等原生连接 JavaScript 的终端技术解决方案。然而,基于JavaScript的接口在JavaScriptCore自身的性能和层消耗方面存在瓶颈。
目前,闲鱼团队正在积极研究和探索Flutter的业务实践,寻找更加高效有效的跨平台解决方案。 Flutter作为跨平台技术,有哪些优势呢?
- Flutter 在 Release 模式下将 Dart 直接编译为本地机器代码,避免了代码翻译和运行时的使用。
- Dart 本身针对高刷新率进行了内存优化(就像每秒 60 帧的屏幕),使得 Dart 运行时可以轻松地在屏幕上绘图。
- Flutter 使用自己的图形来避免 Native 绑定。
Flutter 在应用层使用 Dart 进行开发,支持它的是 C++ 开发的引擎。
为了更好地实施和实施,我们需要深入引擎,了解它的原理和实施系统。线程一直是开发中的一个有问题的话题。我们在实践中也发现了很多陷阱。本文将讨论Flutter引擎的线程方法。
Flutter 线程管理
Flutter Engine 要求 Embeder 提供四个 Task Runner。 Embeder是指将引擎移至平台的中间层代码。这四个主要运行器包括:
Platform Task Runner
Flutter Engine 的 Main Task Runner,类似于 Android Main Thread 或 iOS Main Thread。但需要注意的是,它们之间是有区别的。
一般情况下,Flutter应用启动时会创建一个Engine实例,当Engine创建时会创建一个线程供Platform Runner使用。
与 Flutter Engine 的所有交互(连接调用)都必须在 Plateplate 线程上完成,否则可能会出现意想不到的效果。这类似于必须在主线程上执行的 iOS UI 相关函数。请注意,Flutter Engine 中的许多模块都是不安全的。
规则很简单。对 Flutter 引擎的所有调用都必须在平台线程上进行。
禁用平台轮不会导致 Flutter 应用直接冻结(与 iOS Android 游戏不同)。尽管如此,不建议对这款跑步者进行繁重的工作。如果Platform Thread应用程序长时间挂起,则可能会被Watchdog系统杀死。
UI Task Runner 线程(Dart Runner)
UI Task Runner 用于执行 Dart 根隔离代码(稍后会讨论隔离,我们将其视为 Dart VM 中的线程)。它有自己的根源。它绑定了Flutter执行发布相关任务所需的许多函数和方法。对于每一帧,引擎应该做的是:
- 根隔离通知 Flutter 引擎需要渲染一帧。
- Flutter引擎通知区域需要在下一个vsync时得到通知。
- 平台正在等待下一个vsync
- 设置已创建的对象和小部件并创建层树,该树会立即提交到Flutter Engine。此步骤中不进行光栅化,此步骤生成需要完成的操作的描述。
- 使用语义信息创建或更新树以在屏幕上显示小部件。该对象主要用于配置和发布与平台相关的辅助功能插件。
除了执行相关逻辑之外,Root Isolate 还处理来自本机插件、定时器、微任务和异步 IO 操作的消息。根隔离负责创建和管理定义屏幕上绘制内容的图层树。所以,这条线的多余,就会直接导致和平。
GPU Task Runner
GPU Task Runner主要用于在GPU硬件上执行指令。 UI Task Runner 创建的 Layer Tree 是跨平台的,并且不关心谁完成绘制。 GPU Task Runner负责将Layer Tree提供的信息转换为可以在平台上执行的GPU指令。 GPU Task Runner 负责管理渲染所需的GPU 资源。主要资源包括Framebuffer、Surface、Texture和Buffers字段等。
一般来说,UI Runner 和 GPU Runner 在不同的线程上运行。 GPU Runner会根据当前帧执行的进度向UI Runner查询下一帧的数据。当任务繁重时,它可能会告诉UI Runner延迟任务。这种调度机制保证了GPU Runner不会过载,并避免UI Runner不必要的消耗。
建议为每个引擎实例创建专用的GPU Runner线程。
IO任务运行器
前面讨论的运行器对应用程序能力要求很高。系统可能会导致 WatchDog 被杀,如果 UI 和 GPU Runner 过载,可能会导致 Flutter 应用程序冻结。但是GPU线程需要的一些功能,比如IO,在哪里呢?答案是IO Runner。
IO Runner的主要作用是从图像存储(如磁盘)中读取压缩图像格式,并对图像数据进行处理,为GPU Runner输出做准备。 IO Runner必须首先读取压缩的图像二进制数据(例如PNG、JPEG),将其解压并转换为GPU可以处理的格式,然后将数据加载到GPU中。
访问像 ui.Image 这样的资源只能通过异步调用来调用。当调用到达时,Flutter Framework 告诉 IO Runner 对负载执行异步操作。
IO runner直接决定了加载图片等资源的延迟,从而间接影响性能。因此建议为IO Runner创建专用线程。
现在每个平台实现当前的Runner线程现在都有其实现策略。
iOS 和 Android
移动平台上的每个 Engine 实例在启动时都会为 UI、GPU 和 IO Runner 创建一个新主题。所有引擎实例共享相同的 Platform Runner 和线程。
Fuchsia
每个引擎实例都会为 UI、GPU、IO 和 Platform Runner 创建自己的新标头。
匹配线程设置的可能解决方案
我们发现Platform Runner和Thread分布在移动平台上。引擎的源代码如下:
这里我们可以进行更改,让每个引擎启动自己的线程:
理论上你可以使用任何线程,但最好遵循最佳实践。
详细代码介绍
iOS平台Android可以参考Flutter Engine源码:
Dart隔离机制

隔离定义
隔离是Dart同步方法的一种实现。正在运行的 Dart 程序由一个或多个参与者组成,这也与 Dart 概念不同。隔离是一个具有专用内存和单线程控制的运行组件。 Isolate本身意味着“分离”,因为内存在isolate之间是逻辑隔离的。隔离中的代码是顺序执行的,所有Dart程序并发都是多次隔离执行的结果。因为Dart没有共享内存,也没有争用,所以不需要锁,也不需要担心宕机。
独立机之间的通信
由于独立机之间没有共享内存,因此它们之间通信的唯一方式是通过端口,并且发送到 Dart 的消息并不总是一致的。
isolated线程和普通线程的区别
我们可以看到isolated看似线程,但实际上两者有很大的区别。操作系统中的线程之间可能存在共享内存,但没有隔离。这是最大的区别。
isolate应用简要说明
我们可以阅读Dart源码中的http://isolate.cc文件来查看isolate的具体实现。我们可以看到,创建isolate时主要有以下几个步骤:
- 初始化isolate数据结构
- 初始化堆内存(Heap)
- 插入新创建的isolate并传输到线程中。一对一隔离
- 配置端口
- 配置消息处理机制(消息处理程序)
- 配置调试器(如有必要)
- 将isolate写入全局监视器让我们看看isolate是否开始运行基本代码:
我们可以看到 Dart 本身拉动隔离和线程。事实上,下层仍然使用操作系统提供的OSThread。
Flutter Engine Runners 和 Dart Isolate
有朋友可能会问 Flutter Engine 有自己的 Runners,为什么还需要 Dart 的 Isolate?他们之间是什么关系?
那么我们就得从Runner自己的应用开始了。跑步者是一个抽象的概念。我们可以向runner提交一个任务,任务被放到自己的线程中执行。这与 iOS GCD 执行顺序非常相似。 。当我们查看 iOS Runner 应用程序时,会发现有一个循环。这个循环就是CFRunloop。跑步者自己的iOS平台应用程序是CFRunloop。提交的任务被放入CFRunloop中执行。
Dart 的 Isolate 由 Dart 虚拟机本身管理,不能直接被 Flutter Engine 访问。 Root Isolate 利用 Dart 的 C++ 回调能力,将 UI 渲染相关的任务传递给 UI Runner 执行,从而可以与 Flutter Engine 的相关模块进行交互。 Flutter UI 相关的任务被路由到 UI Runner,UI Runner 还可以向 Isolate 提供某些事件通知。 ,UI Runner 还处理来自应用程序本机插件的任务。
简单来说,Dartisolate和FlutterRunner是独立的。他们通过工作协调机制协同工作。
一个陷阱与泪水的故事
了解Flutter Engine的原理和Dart虚拟机的异步实现,可以让我们避免陷阱,更轻松高效地开发。在项目实施阶段,我们踩过很多坑洼,不断学习坑洼开挖和坑洼填充的过程。这里我简单说一下其中一个特殊情况:当时我们需要导入原生图像数据并注册到Engine中来创建Texture渲染。使用完资源后,我们需要将其删除。看似显而易见的逻辑催生了动物指数。问题。后来发现注册是在子线程中完成的,但是删除是在Platform线程中完成的。定义好线程结构后,问题就解决了。
结论
本文主要讨论了Flutter引擎层面的线程调度管理和Dart的隔离机制。深入了解Flutter的编码机制后,我们在开发过程中就能更加得心应手。在理解 Flutter 设计的过程中,我们受到启发,为应用程序开发了一个类似的线程系统。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。