加载顺序、性能和内存
本页面描述了显示 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,并且多个 Isolate 可以由同一个 Dart VM 托管。
在 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 组件时(例如,通过使用 FlutterActivity.withCachedEngine()
在 Android 上使用 startActivity()
和 Intent
构建的 Intent
)。或者,通过使用 initWithEngine: nibName: bundle:
在 iOS 上呈现由 FlutterViewController
初始化的 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 驱动程序内存。
- Dart 运行时管理的内存约 1 MB。
- Dart 加载的字体映射约 5 MB。
在延迟方面,成本示例(取决于用例而异)可能是
- 从应用程序包收集 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 相关的内存。这不会影响 Flutter 状态(除非也释放了 FlutterEngine
)。
有关创建多个 FlutterEngine
的性能详细信息,请参阅 多个 Flutter。
除非另有说明,否则本网站上的文档反映了 Flutter 的最新稳定版本。页面上次更新于 2024-11-19。 查看源代码 或 报告问题。