Code前端首页关于Code前端联系我们

Flutter线程管理与Dart隔离机制

terry 2年前 (2023-09-23) 阅读数 67 #移动小程序

随着终端业务需求越来越复杂,版本迭代越来越频繁,需要多种集成跨平台开发的最优解决方案,以提高研发效率。目前,有 RN 和 Weex 等原生连接 JavaScript 的终端技术解决方案。然而,基于JavaScript的接口在JavaScriptCore自身的性能和层消耗方面存在瓶颈。

目前,闲鱼团队正在积极研究和探索Flutter的业务实践,寻找更加高效有效的跨平台解决方案。 Flutter作为跨平台技术,有哪些优势呢?

  • Flutter 在 Release 模式下将 Dart 直接编译为本地机器代码,避免了代码翻译和运行时的使用。
  • Dart 本身针对高刷新率进行了内存优化(就像每秒 60 帧的屏幕),使得 Dart 运行时可以轻松地在屏幕上绘图。
  • Flutter 使用自己的图形来避免 Native 绑定。

Flutter 在应用层使用 Dart 进行开发,支持它的是 C++ 开发的引擎。 Flutter线程管理与Dart Isolate机制

为了更好地实施和实施,我们需要深入引擎,了解它的原理和实施系统。线程一直是开发中的一个有问题的话题。我们在实践中也发现了很多陷阱。本文将讨论Flutter引擎的线程方法。

Flutter 线程管理

Flutter Engine 要求 Embeder 提供四个 Task Runner。 Embeder是指将引擎移至平台的中间层代码。这四个主要运行器包括: Flutter线程管理与Dart Isolate机制

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应用程序长时间挂起,则可能会被W​​atchdog系统杀死。

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分布在移动平台上。引擎的源代码如下: Flutter线程管理与Dart Isolate机制

这里我们可以进行更改,让每个引擎启动自己的线程: Flutter线程管理与Dart Isolate机制

理论上你可以使用任何线程,但最好遵循最佳实践。

详细代码介绍

iOS平台Android可以参考Flutter Engine源码: Flutter线程管理与Dart Isolate机制

Dart隔离机制

Flutter线程管理与Dart Isolate机制

隔离定义

隔离是Dart同步方法的一种实现。正在运行的 Dart 程序由一个或多个参与者组成,这也与 Dart 概念不同。隔离是一个具有专用内存和单线程控制的运行组件。 Isolate本身意味着“分离”,因为内存在isolate之间是逻辑隔离的。隔离中的代码是顺序执行的,所有Dart程序并发都是多次隔离执行的结果。因为Dart没有共享内存,也没有争用,所以不需要锁,也不需要担心宕机。

独立机之间的通信

由于独立机之间没有共享内存,因此它们之间通信的唯一方式是通过端口,并且发送到 Dart 的消息并不总是一致的。

isolated线程和普通线程的区别

我们可以看到isolated看似线程,但实际上两者有很大的区别。操作系统中的线程之间可能存在共享内存,但没有隔离。这是最大的区别。

isolate应用简要说明

我们可以阅读Dart源码中的http://isolate.cc文件来查看isolate的具体实现。我们可以看到,创建isolate时主要有以下几个步骤:

  • 初始化isolate数据结构
  • 初始化堆内存(Heap)
  • 插入新创建的isolate并传输到线程中。一对一隔离
  • 配置端口
  • 配置消息处理机制(消息处理程序)
  • 配置调试器(如有必要)
  • 将isolate写入全局监视器让我们看看isolate是否开始运行基本代码:Flutter线程管理与Dart Isolate机制

    我们可以看到 Dart 本身拉动隔离和线程。事实上,下层仍然使用操作系统提供的OSThread。 Flutter线程管理与Dart Isolate机制

    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前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门