Skip to main content

加载顺序、性能和内存

本页描述了显示 Flutter UI 所涉及步骤的细分。了解这一点,您可以更好地、更明智地决定何时预热 Flutter 引擎,哪些操作在哪个阶段是可能的,以及这些操作的延迟和内存成本。

加载 Flutter

#

Android 和 iOS 应用(集成到现有应用中支持的两个平台)、完整的 Flutter 应用和添加到应用模式在显示 Flutter UI 时具有类似的概念加载步骤序列。

查找 Flutter 资源

#

Flutter 的引擎运行时和您的应用程序的已编译 Dart 代码都作为共享库捆绑在 Android 和 iOS 上。加载 Flutter 的第一步是在您的 .apk/.ipa/.app 中找到这些资源(以及其他 Flutter 资源,例如图像、字体和 JIT 代码,如果适用)。

这发生在您首次在 AndroidiOS 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 上的 SurfaceiOS 上的 CAEAGLLayerCAMetalLayer

此时,您的 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