从现在开始,我们进入 Flutter 基础学习模块。首要任务便是了解 Flutter 框架的设计以及编译时的原理性知识,有了这些知识,可以更深刻地了解 Flutter 的整体设计理念,也能解决很多曾困惑我们的问题,比如在 Flutter 中,是如何做到在编码完成后,仅靠点击"运行"按钮,或执行命令行语句,就能在不同平台上产出相应平台的应用程序的。
分层式架构
Flutter 框架整体遵循分层式架构设计 而来,客户端和 Web 端又各具特色。上层组件依赖下层组件 ,组件之间无法跨层访问。
这种分层的、模块化的架构设计其实不止在 Flutter 中存在,对于 Android 而言也很类似。这种设计的好处在于结构清晰、易于维护、组织灵活、数据安全等特点。
结构清晰不必多说,我们从稍后的架构设计图中我们便可体会。
另外,由于每个模块较为独立,符合"高内聚、低耦合"的理念,因此在后期维护时,改动某个模块的代码对相关模块的影响较小甚至毫无影响。组织灵活在框架层体现得更加明显,如果我们的程序是完全遵循 Material Design,Cupertino 模块就可以移除。如果打造的是 iOS 风格的程序,则可移除 Material 模块。甚至可以使用其它的组件库,如此便不再需要 Material 和 Cupertino 模块了。
数据安全也是模块化和分层设计后带来的优势,它可以将本层或本模块中重要的数据保存在本层或本模块中,从外部无法直接访问,也就无法对其进行修改,保证了数据的安全性。
当然,这种架构设计的优势不止于此,这里就不再详细列举其它方面了。接下来,我们来分别展开讲讲客户端和 Web 端的架构模型,它们有相似的地方,也有很多不同之处。
客户端
我们先看客户端,也就是 PC、macOS、Android 和 iOS(包含 iPadOS,以下概称 iOS)的应用程序。

嵌入层 :之所以 Flutter 客户端应用程序可以运行在各种平台上,最关键的就是嵌入层了, 每个平台都有一个与之对应的嵌入层实现。它的作用是对上层(引擎层)屏蔽了个平台的差异性。具体来说,对于 Windows 和 Linux,使用 C++ 实现;对于 macOS 和 iOS,使用 Objective-C 和 Objective-C++ 实现;对于 Android,则使用 Java 和 C++ 实现。
引擎层:如果说嵌入层是"基础",引擎层则是"核心"。它提供了 Flutter 的核心 API 的具体实现,比如图形绘制、文本布局、本地文件 IO、网络请求 IO、编译工具等等。引擎还将提供栅格化能力,以便将每帧图像输出到屏幕上。引擎层使用 C/C++ 实现,用 Dart 编程语言重新包装,以便为上层(框架层)服务。
💡 提示:栅格化是将矢量图形格式表示的图像转换成位图以用于显示器或者打印机输出的过程。
框架层 :框架层是开发者与 Flutter 打交道最直接的方式,它是用 Dart 编程语言实现的响应式框架,内置了丰富的"开箱即用"的能力。比如:Material(Android)/Cupertino(iOS) 风格的组件、动画、手势等等。框架层中的各个模块都是可选的,也是可替代的。开发者可以从官方的仓库中集成更多的库来支撑具体业务。
Web端
我们再来看 Web 端,和客户端相比,Web 端直接运行在浏览器上,无需嵌入层。

编译时,Dart 代码直接编译成 JavaScript 。在实际运行时,所有的内容将以 HTML 和 WebGL 来呈现。前者使用HTML、CSS、Canvas 和 SVG 进行渲染,优势在于代码量小;后者使用 CanvasKit 进行渲染,优势在于图形性能。
💡 提示:CanvasKit 是 WebAssembly 的 Skia 版本。
开发者优势
对比两张架构图发现,最上面的框架层是相同的。在实际编码时,二者区别也没有太大差异。这对于开发者而言确实是一个福音,可以真正做到编写一次代码,所有平台都能适配了。甚至连编译时的热加载也能全平台支持。
当然,某些平台专属能力还需要做针对性处理,比如蓝牙、NFC 等等。
接下来,让我们把目光聚焦到客户端。既然 Flutter 能在不同平台上运行,那会不会造成安装包体积过大,或是性能低下等问题呢?
要解决这个疑惑,就要说说 Flutter 程序是如何编译,以及如何运行起来的了。
代码是怎么"跑"起来的?
在课程最开始的简介里我曾经提到,Flutter 是采用自绘制引擎的 。也就是说,我们虽然可以在 Flutter 中使用不同平台风格的组件,但从根本上说并不是直接使用了原生组件,而是使用了 Flutter 中提供的和原生一模一样的组件。这些组件的绘制和原生平台无关,也就无需考虑兼容性和使用限制等问题,因此性能上不会受兼容性的影响而降低。
与之相反,由于组件完全自绘制,为开发者提供了相当灵活的使用方式。使某些原生组件不支持的逻辑和手势操作变为可能。开发者甚至可以把现有组件重新排列组合,打造自定义组件。而这一切和"平台兼容性"无关,丝毫不会影响性能。
对于 Android,Flutter 引擎部分使用 NDK 实现,Dart 代码部分将编译为 ARM/x86 运行库,最后将这些内容一同打包成 APK 以便分发。据官方统计,Flutter 引擎在 ARM32 下占用大约 4.3 MB,ARM64 下占用大约 4.6 MB 的磁盘空间。
对于 iOS,Flutter 引擎部分使用 LLVM 编译,Dart 代码部分将编译为 ARM 运行库,最后将这些内容一同打包成 IPA 以便分发。遗憾的是,Apple 需要对 IPA 中的二进制文件进行加密,压缩率降低,需要占用大约 10.9 MB的磁盘空间。
💡 提示:目前来看,无需担心 Apple 的审核机制。一些由 Flutter 打造的 App 已经荣获最佳设计应用称号。
上述结论来自官方在 2021 年 3 月得到的结论,使用的是一个相当简单的 Flutter App 为例。大家可使用下列命令行分析自己的程序在不同平台下的磁盘占用情况:
-
flutter build apk --analyze-size
-
flutter build appbundle --analyze-size
-
flutter build ios --analyze-size
-
flutter build linux --analyze-size
-
flutter build macos --analyze-size
-
flutter build windows --analyze-size
❗️ 注意:必要时,需要添加 --target-platform 来指明特定的 CPU 结构。
相信大家一眼看上去都能清楚它们各自对应的平台吧,我这里就不再一一对应了。这里放上一个我自己运行后的分析结果,供大家参考:

大家注意看 lib 目录中的磁盘占用情况,这个程序中我用了一些第三方库,所以从结果上看比官方的统计会大一些。
小结
🎉 恭喜,您完成了本次课程的学习!
📌 以下是本次课程的重点内容总结:
本讲为大家简要介绍了 Flutter 框架以及程序编译和运行的基本原理。
无论是客户端还是 Web 端,Flutter 都遵循分层式架构 设计。其最重要的特点就是上层组件依赖下层组件,组件之间无法跨层访问。
对于客户端,是三层架构。从下到上分别是嵌入层、引擎层 和框架层。嵌入层相当于"平台兼容层",引擎层提供了Flutter 核心 API 和具体实现,框架层与开发者直接打交道,模块可选、可替代。
对于 Web 端,无需考虑平台兼容性,因此是两层架构。从下到上分别是浏览器 和框架层。所有的内容将以 HTML 和WebGL 来呈现。
编译时,Android 和 iOS 都将把 Dart 源码封装成库,与 Flutter 引擎一同打包到安装程序 。有一定的代码保护作用,对性能影响可以忽略。
➡️ 在下次课程中,我们继续 Flutter 基础方面的介绍,内容是"组件"。在 Flutter 中"一切皆组件",从这个说法中足以说明组件的重要性,下一讲就来攻克它。