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 生态贡献一份力量。




相关文章
|
缓存 安全 前端开发
语雀桌面端技术架构实践
语雀桌面端技术架构实践
348 0
|
弹性计算 开发框架 运维
阿里云无影云桌面-桌面即服务的架构演进
无影是终端用户计算产品线,由云桌面、云应用、云数据、终端等共同组成。无影云桌面,在架构上,也经历了几次大的架构调整和升级。无影云桌面是一个Desktop As A Service产品。
3194 3
阿里云无影云桌面-桌面即服务的架构演进
|
缓存 安全 测试技术
语雀桌面端技术架构实践
语雀桌面端作为语雀为用户提供的生产力工具,上线两年多来一直保持高频的迭代和健康的业务增长。本次主要介绍我们在做桌面端时的一些技术架构思考和实践,同时也将分享我们沉淀的一些通用桌面应用解决方案和经验。
666 0
语雀桌面端技术架构实践
|
负载均衡 安全 容灾
虚拟桌面架构的高性能体验云上部署
自建虚拟化桌面架构迁移阿里云,利用云上资源灵活性和规模化优势/构建云上虚拟桌面服务
虚拟桌面架构的高性能体验云上部署
|
10天前
|
缓存 负载均衡 JavaScript
探索微服务架构下的API网关模式
【10月更文挑战第37天】在微服务架构的海洋中,API网关犹如一座灯塔,指引着服务的航向。它不仅是客户端请求的集散地,更是后端微服务的守门人。本文将深入探讨API网关的设计哲学、核心功能以及它在微服务生态中扮演的角色,同时通过实际代码示例,揭示如何实现一个高效、可靠的API网关。
|
9天前
|
Cloud Native 安全 数据安全/隐私保护
云原生架构下的微服务治理与挑战####
随着云计算技术的飞速发展,云原生架构以其高效、灵活、可扩展的特性成为现代企业IT架构的首选。本文聚焦于云原生环境下的微服务治理问题,探讨其在促进业务敏捷性的同时所面临的挑战及应对策略。通过分析微服务拆分、服务间通信、故障隔离与恢复等关键环节,本文旨在为读者提供一个关于如何在云原生环境中有效实施微服务治理的全面视角,助力企业在数字化转型的道路上稳健前行。 ####
|
9天前
|
Dubbo Java 应用服务中间件
服务架构的演进:从单体到微服务的探索之旅
随着企业业务的不断拓展和复杂度的提升,对软件系统架构的要求也日益严苛。传统的架构模式在应对现代业务场景时逐渐暴露出诸多局限性,于是服务架构开启了持续演变之路。从单体架构的简易便捷,到分布式架构的模块化解耦,再到微服务架构的精细化管理,企业对技术的选择变得至关重要,尤其是 Spring Cloud 和 Dubbo 等微服务技术的对比和应用,直接影响着项目的成败。 本篇文章会从服务架构的演进开始分析,探索从单体项目到微服务项目的演变过程。然后也会对目前常见的微服务技术进行对比,找到目前市面上所常用的技术给大家进行讲解。
23 1
服务架构的演进:从单体到微服务的探索之旅
|
7天前
|
消息中间件 监控 安全
后端架构演进:从单体到微服务####
在数字化转型的浪潮中,企业应用的后端架构经历了从传统单体架构到现代微服务架构的深刻变革。本文探讨了这一演进过程的背景、驱动力、关键技术及面临的挑战,揭示了如何通过微服务化实现系统的高可用性、扩展性和敏捷开发,同时指出了转型过程中需克服的服务拆分、数据管理、通信机制等难题,为读者提供了一个全面理解后端架构演变路径的视角。 ####
24 8