加载顺序、性能和内存
本页描述了显示 Flutter UI 所涉及步骤的细分。了解这一点,您可以更好地、更明智地决定何时预热 Flutter 引擎,哪些操作在哪个阶段是可能的,以及这些操作的延迟和内存成本。
加载 Flutter
#Android 和 iOS 应用(集成到现有应用中支持的两个平台)、完整的 Flutter 应用和添加到应用模式在显示 Flutter UI 时具有类似的概念加载步骤序列。
查找 Flutter 资源
#Flutter 的引擎运行时和您的应用程序的已编译 Dart 代码都作为共享库捆绑在 Android 和 iOS 上。加载 Flutter 的第一步是在您的 .apk/.ipa/.app 中找到这些资源(以及其他 Flutter 资源,例如图像、字体和 JIT 代码,如果适用)。
这发生在您首次在 Android 和 iOS API 上构造 FlutterEngine
时。
加载 Flutter 库
#找到后,引擎的共享库将每个进程内存加载一次。
在 Android 上,这也发生在构造 FlutterEngine
时,因为 JNI 连接器需要引用 Flutter C++ 库。在 iOS 上,这发生在首次运行 FlutterEngine
时,例如通过运行 runWithEntrypoint:
。
启动 Dart VM
#Dart 运行时负责管理 Dart 代码的 Dart 内存和并发。在 JIT 模式下,它还负责在运行时将 Dart 源代码编译成机器代码。
在 Android 和 iOS 上,每个应用程序会话存在单个 Dart 运行时。
在 Android 上首次构造 FlutterEngine
时,以及在 iOS 上首次运行 Dart 入口点 时,都会执行一次性的 Dart VM 启动。
此时,您的 Dart 代码的快照 也从应用程序的文件中加载到内存中。
如果您直接使用Dart SDK,而没有使用 Flutter 引擎,也会发生这个通用过程。
Dart VM 启动后永不关闭。
创建和运行 Dart Isolate
#在 Dart 运行时初始化后,Flutter 引擎对 Dart 运行时的使用是下一步。
这是通过在 Dart 运行时启动一个Dart Isolate
来完成的。Isolate 是 Dart 用于内存和线程的容器。此时还会在主机平台上创建许多辅助线程 来支持 Isolate,例如用于卸载 GPU 处理的线程和另一个用于图像解码的线程。
每个 FlutterEngine
实例存在一个 Isolate,并且同一个 Dart VM 可以托管多个 Isolate。
在 Android 上,当您在 FlutterEngine
实例上调用DartExecutor.executeDartEntrypoint()
时,就会发生这种情况。
在 iOS 上,当您在 FlutterEngine
上调用 runWithEntrypoint:
时,就会发生这种情况。
此时,将执行您的 Dart 代码的选定入口点(默认情况下是您的 Dart 库 main.dart
文件的 main()
函数)。如果您在 main()
函数中调用了 Flutter 函数 runApp()
,那么您的 Flutter 应用或库的小部件树也将被创建和构建。如果您需要阻止 Flutter 代码中某些功能的执行,则 AppLifecycleState.detached
枚举值表示 FlutterEngine
未附加到任何 UI 组件,例如 iOS 上的 FlutterViewController
或 Android 上的 FlutterActivity
。
将 UI 附加到 Flutter 引擎
#标准的完整 Flutter 应用会在应用启动后立即进入此状态。
在添加到应用场景中,当您将 FlutterEngine
附加到 UI 组件时,就会发生这种情况,例如通过使用 Android 上使用 FlutterActivity.withCachedEngine()
构建的 Intent
调用 startActivity()
, 或者通过在 iOS 上呈现使用 initWithEngine: nibName: bundle:
初始化的 FlutterViewController
。
如果您在没有预热 FlutterEngine
的情况下启动了 Flutter UI 组件,例如在 Android 上使用 FlutterActivity.createDefaultIntent()
,或者在 iOS 上使用 FlutterViewController initWithProject: nibName: bundle:
,也会出现这种情况。在这些情况下,会隐式创建 FlutterEngine
。
在后台,这两个平台的 UI 组件都为 FlutterEngine
提供渲染表面,例如 Android 上的 Surface
或 iOS 上的 CAEAGLLayer 或 CAMetalLayer。
此时,您的 Flutter 程序生成的 Layer
树(每帧)将转换为 OpenGL(或 Vulkan 或 Metal)GPU 指令。
内存和延迟
#显示 Flutter UI 具有非平凡的延迟成本。通过提前启动 Flutter 引擎可以减少此成本。
添加到应用场景中最相关的选择是决定何时预加载 FlutterEngine
(即加载 Flutter 库、启动 Dart VM 并在一个 Isolate 中运行入口点),以及预热的内存和延迟成本是多少。您还需要了解预热如何影响随后将 UI 组件附加到该 FlutterEngine
时渲染第一帧 Flutter 的内存和延迟成本。
截至 Flutter v1.10.3,并在发布版 AOT 模式下对 2015 年低端设备进行测试,预热 FlutterEngine
的成本为:
- 在 Android 上预热需要 42 MB 和 1530 毫秒。其中 330 毫秒是在主线程上的阻塞调用。
- 在 iOS 上预热需要 22 MB 和 860 毫秒。其中 260 毫秒是在主线程上的阻塞调用。
可以在预热期间附加 Flutter UI。剩余时间将加入到首帧延迟时间。
就内存而言,成本样本(取决于用例而变化)可能是:
- 创建 pthreads 需要约 4 MB 的操作系统内存。
- 约 10 MB 的 GPU 驱动程序内存。
- 约 1 MB 的 Dart 运行时管理的内存。
- 约 5 MB 的 Dart 加载的字体映射。
就延迟而言,成本样本(取决于用例而变化)可能是:
- 从应用程序包收集 Flutter 资源约需 20 毫秒。
- dlopen Flutter 引擎库约需 15 毫秒。
- 创建 Dart VM 并加载 AOT 快照约需 200 毫秒。
- 加载 Flutter 相关的字体和资源约需 200 毫秒。
- 运行入口点、创建第一个小部件树并编译所需的 GPU 着色器程序约需 400 毫秒。
应足够晚地预热 FlutterEngine
以延迟所需的内存消耗,但又足够早以避免将 Flutter 引擎启动时间与显示 Flutter 的第一帧延迟结合起来。
确切的时间取决于应用程序的结构和启发式方法。一个例子是在 Flutter 绘制屏幕之前的屏幕中加载 Flutter 引擎。
在引擎预热的情况下,UI 附加时的第一帧成本为:
- 在 Android 上为 320 毫秒,另外还有 12 MB(高度依赖于屏幕的物理像素大小)。
- 在 iOS 上为 200 毫秒,另外还有 16 MB(高度依赖于屏幕的物理像素大小)。
就内存而言,成本主要是用于渲染的图形内存缓冲区,并且取决于屏幕大小。
就延迟而言,成本主要是等待操作系统回调为 Flutter 提供渲染表面并编译其余无法预先预测的着色器程序。这是一次性成本。
释放 Flutter UI 组件时,将释放 UI 相关的内存。这不会影响 FlutterEngine
中存在的 Flutter 状态(除非也释放了 FlutterEngine
)。
有关创建多个 FlutterEngine
的性能详细信息,请参见多个 Flutter。
除非另有说明,否则本网站上的文档反映的是 Flutter 的最新稳定版本。页面最后更新于 2025-01-30。 查看源代码 或 报告问题。