FlutterEngine 桌面端架构浅析

简介: FlutterEngine 桌面端架构浅析

1 前言

在钉钉 Flutter 桌面端落地过程中,我们遇到了很多仅仅依赖 Flutter 官方文档无法解决的问题,例如:桌面端集成模式问题、内存泄露问题、卡顿问题、光标焦点异常问题等。由于无法直接通过官方文档得到答案,我们便尝试通过分析源码实现和设计文档来寻找解决办法。虽然最终大部分问题得以解决,但是在这过程中有两点一直困扰我们:

  1. Flutter 生态中桌面端相关资料极少。少部分官方公开资料也仅有比较宽泛的介绍,缺少详细方案设计信息;业界对 FlutterEngine 架构分析和讨论,大多也仅仅设计移动端,桌面端相关内容很少涉及;
  2. FlutterEngine 在移动端和桌面端 Embedder 层设计有较大差异,移动端相关资料/方案无法直接应用到桌面端。

因此我们便萌生了整理一份 Flutter 桌面端资料集的想法。一方面来作为 Flutter 桌面端设计的入门资料,降低大家上手学习桌面端引擎设计的门槛、提升效率;另外一方面也可作为工具手册,为后续我们可能逐步落地的桌面端引擎改造提供技术储备。

本文主要从宏观角度来介绍一下 FlutterEngine 桌面端设计,从发展历史、架构概述、与移动端引擎对比等角度做一下阐述,以期望读者通过本文的介绍,能对 FlutterEngine 桌面端实现方案有一个整体的了解。

2 发展脉络

通过查阅各种资料我们发现,Flutter 目标虽然是提供一套可跨多端的 UI 套件,但是 Flutter Desktop 项目发展最初并未被直接纳入 Flutter 项目。不过 Flutter Desktop 主要技术人员仍然来自 Google 团队,Flutter Desktop 项目前期独立 Flutter 主项目发展,或许是出于项目管理上的考虑。

Flutter Desktop 发展初期 git 项目为 flutter-desktop-embedding[1], 技术讨论组为Desktop Embedding for Flutter[2]。通过分析 git commit 记录以及讨论主题,我们大致可梳理出 Flutter Desktop 发展脉络时间轴:

image.png


3 架构对比

Flutter 桌面端与移动端引擎实现差异主要聚焦在两部分:

  1. 是否使用 Embedder API;
  2. 如何处理 Windows Resize。

下面针对上述两点分别介绍一下。

3.1 Embedder API

关于 Flutter 架构设计,最权威的资料当然来自官方团队提供文档(来源[3]):

image.png

image.png

注:由于 Web 平台下的实现与其它平台有较大差异、且钉钉在实际应用中并未涉及,因此在此不再分析。

无论是移动端还是桌面端,Flutter 三层结构中的 Framework(Dart) 以及 Engine(C++) 实现是共享的,实现上的主要差异聚焦在 Platform 集成上。

在梳理 Flutter Desktop 桌面端发展脉络时我们已经知道,桌面端实现整体建立在 Embedder API 基础之上(Commit[4]):

image.png

由下至上我们对上图做一下简单说明:

  1. Shell 即 FlutterEngine 平台无关的核心实现部分,如 Dart Runtime、Skia等;
  2. Embedder API是 Flutter 封装的平台无关接口层,其主要价值在于封装底层实现、简化接入流程、降低新平台接入成本;
  3. Android/iOS/macOS/Windows Embedder 是平台接入层,各端根据根据平台规则注入 Flutter 运行所依赖的部分,比如 渲染画布、线程管理、插件机制等;
  4. Android/iOS/macOS/Windows Interface是开发接口层,即 Flutter 面向不同平台暴露的使用 API。虽然不同语言暴露 API 的形式可能略有差异,但是在接口语义上四端基本一致。

3.2 Desktop Resize

在进一步分析 FlutterEngine 桌面端源码时,我们发了其相比移动端实现,除了多一层 Embedder API 以外,还有一层用于管理桌面窗口大小变化的模块FlutterResizeSynchronizer

在查阅相关资料之后找到此模块相关的 设计文档

  • How to handle resize in desktop embeddings[6]
  • Handling flutter view resizing on macOS[7]
  • Support double buffering for window resizing[8]

在设计文档中有对此问题的说明:

image.png

简单来说即 Flutter 在异步线程渲染与 Window 在主线程变化,会导致出现 Crash、重影等一系列问题。FlutterResizeSynchronizer 的出现即为了解决此类问题。

4 实现分析

本小结我们以 macOS Embedder 为例,来分下一下 Flutter 桌面端集成部分的实现。为了方便移动端的同学更好理解,部分内容会以 iOS 端实现来做对比说明。

4.1 类图

下面这种即根据 3.1 小节的架构简图,结合 FlutterEngine macOS 端实现梳理出的核心类图:

image.png

为了便于对比理解,我们在看看 iOS 端对应的核心类图:

image.png

通过对比上两张类图,我们可以初步得到以下信息:

  1. 桌面端部分实现相比移动端增加一层跨平台的 Embedder API,虽然理论上可简化部分上层实现,但是因为涉及到多渲染模式图层合成等因素的影响,桌面端整体架构复杂度并不低于移动端
  2. Desktop Resize 主要由 FlutterView 来控制,在移动端端中 FlutterView 实现较薄,主要用于承载 Flutter 所需的画布;但是在桌面端其功能更为复杂,已直接参与到 Flutter 绘制流程中,并在其中起到关键作用;
  3. Shell 层做了较好的抽象,绝大部分场景可做到实现与平台无关;

4.2 流程对比

通过4.1小节的内容我们可知,FlutterEngine 在桌面端和移动端的差异主要聚焦在渲染绘制阶段。

根据 Flutter 官方分享资料我们知道,Flutter 渲染大致分为两个阶段:

  1. 第一阶段主要在 UI 线程工作,大部分由 Dart 层的 Flutter Framework 实现;
  2. 第二阶段在 CPU 线程工作,主要由 FlutterEngine 中的 C++ 层模块实现。

针对以上两点,第一阶段无论什么平台基本都是一致的,下面我们就结合实现大致对比一下 iOS 端与 macOS 端的第二阶段流程差异。

阶段 iOS macOS
1 DoDraw DoDraw
2 DrawToSurface DrawToSurface
3 AcquireFrame AcquireFrame
4 Submit Submit
5 Present Try Present(窗口变化中则同步等待)
6 Flush DoFlush

注意:上述流程对比仅供示意说明问题,并非严谨流程图,有很多细节表格中并未体现

阶段 5~6 所示即 3.2 小节讨论到的「Desktop Resize」控制模块,结合 4.1 中的类图,渲染链路最终会沿着红线所表示的链路回到 FlutterView 模块,最终由 FlutterView + FlutterResizeSynchronizer + FlutterResizableBackingStoreProvider + FlutterSurfaceManager 相互配合,完成安全可靠的绘制:

image.png移动端应为不涉及窗口大小变化,因为并无此流程。

通过上述对比我们可知,Flutter 桌面端相比移动端实现,渲染流程主要变化在于增加窗口大小变化管理模块,窗口大小变化和 Flutter 页面渲染存在同步影响

  1. 如果在渲染过程中窗口发生变化,则变化动作需要等待渲染流程结束之后才可响应:可能导致 Native 主进程卡顿;
  2. 如果 Flutter 渲染过程中存在窗口变化,则会在窗口变化结束之后才会响应渲染:可能影响 Flutter 侧动画效果等;

在钉钉桌面端落地过程中,我们即处理过因为上述同步规则导致的 Flutter 页面创建阶段卡顿,最终通过尽量避免 Window 变化的方式来绕过。

5 小结

本文主要梳理了一下 FluttterEngine 桌面端发展脉络,并针对 FlutterEngine 桌面实现核心内容做了梳理。

通过第3和第4两个小节的分析我们可以看到,Flutter Desktop 最初基于 Embedder API 来实现,或许是期望能够通过通用的 Embedder API 来降低接入成本;并且我们通过查阅 flutter-desktop-embedding[9]commit 记录,最初基于 Embedder API 确实可以做到只通过极少的几个文件,即完成 Flutter Mac 端运行的效果:

但是随着场景复杂度的深入,分层过多带来的弊端逐渐显露出来:在核心任何一个功能增强,都需要对 Platform、Embedder、Shell 三层同时做改造,并且因为要保证 Embedder 和 Shell 层的向兼容性,Embedder 层变得越来越臃肿

Embedder 中负责引擎初始化的 FlutterEngineInitialize 函数为例:

  • 其代码行数约有460行
  • 函数入参合法性校验代码有80行
  • 用于配置启动参数的 FlutterProjectArgs 结构体有34的成员;
  • 用于配置渲染模式的 FlutterRenderConfig 内嵌全部4中渲染模式配置,每种配置10+个不同的成员;
  • 大量的函数指针 Callback.

虽然现在无法判断未来 Flutter Desktop 的实现是否会一直基于 Embedder API 来实现,但基于目前时间点来看,基于 Embedder API 带来的扩展复杂度成本已经逐步掩盖了 Embedder API 所带来封装收益

后面我们会进一步分析 FlutterEngine 桌面端核心流程,并尝试去对齐一些目前移动端支持、但桌面端暂未支持的能力,服务于钉钉业务的同时,也希望能为 Flutter 生态贡献一份力量。




相关文章
|
缓存 安全 前端开发
语雀桌面端技术架构实践
语雀桌面端技术架构实践
319 0
|
弹性计算 开发框架 运维
阿里云无影云桌面-桌面即服务的架构演进
无影是终端用户计算产品线,由云桌面、云应用、云数据、终端等共同组成。无影云桌面,在架构上,也经历了几次大的架构调整和升级。无影云桌面是一个Desktop As A Service产品。
3106 3
阿里云无影云桌面-桌面即服务的架构演进
|
缓存 安全 测试技术
语雀桌面端技术架构实践
语雀桌面端作为语雀为用户提供的生产力工具,上线两年多来一直保持高频的迭代和健康的业务增长。本次主要介绍我们在做桌面端时的一些技术架构思考和实践,同时也将分享我们沉淀的一些通用桌面应用解决方案和经验。
634 0
语雀桌面端技术架构实践
|
负载均衡 安全 容灾
虚拟桌面架构的高性能体验云上部署
自建虚拟化桌面架构迁移阿里云,利用云上资源灵活性和规模化优势/构建云上虚拟桌面服务
虚拟桌面架构的高性能体验云上部署
|
14天前
|
Kubernetes Cloud Native Docker
云原生之旅:从容器到微服务的架构演变
【8月更文挑战第29天】在数字化时代的浪潮下,云原生技术以其灵活性、可扩展性和弹性管理成为企业数字化转型的关键。本文将通过浅显易懂的语言和生动的比喻,带领读者了解云原生的基本概念,探索容器化技术的奥秘,并深入微服务架构的世界。我们将一起见证代码如何转化为现实中的服务,实现快速迭代和高效部署。无论你是初学者还是有经验的开发者,这篇文章都会为你打开一扇通往云原生世界的大门。
|
3天前
|
监控 负载均衡 应用服务中间件
探索微服务架构下的API网关设计与实践
在数字化浪潮中,微服务架构以其灵活性和可扩展性成为企业IT架构的宠儿。本文将深入浅出地介绍微服务架构下API网关的关键作用,探讨其设计原则与实践要点,旨在帮助读者更好地理解和应用API网关,优化微服务间的通信效率和安全性,实现服务的高可用性和伸缩性。
13 3
|
6天前
|
存储 Java Maven
从零到微服务专家:用Micronaut框架轻松构建未来架构
【9月更文挑战第5天】在现代软件开发中,微服务架构因提升应用的可伸缩性和灵活性而广受欢迎。Micronaut 是一个轻量级的 Java 框架,适合构建微服务。本文介绍如何从零开始使用 Micronaut 搭建微服务架构,包括设置开发环境、创建 Maven 项目并添加 Micronaut 依赖,编写主类启动应用,以及添加控制器处理 HTTP 请求。通过示例代码展示如何实现简单的 “Hello, World!” 功能,并介绍如何通过添加更多依赖来扩展应用功能,如数据访问、验证和安全性等。Micronaut 的强大和灵活性使你能够快速构建复杂的微服务系统。
26 5
|
14天前
|
消息中间件 Java 网络架构
AMQP与微服务架构的集成策略
【8月更文第28天】在微服务架构中,各个服务通常通过HTTP/REST、gRPC等协议进行交互。虽然这些方法在很多场景下工作得很好,但在需要高并发、低延迟或需要处理大量消息的情况下,传统的同步调用方式可能无法满足需求。此时,AMQP作为异步通信的一种标准协议,可以提供一种更为灵活和高效的消息传递机制。
18 1

热门文章

最新文章