查看原文
其他

用 Flutter 打包 iOS 应用的一些细节知识

Flutter 谷歌开发者 2021-08-05

作者 / Chinmay Garde, Senior Software Engineer, Google


本文将向大家介绍 Flutter 的构建系统是如何将 Flutter 项目 (及其资源) 转换为 iOS 应用包的。我希望揭开一些构建步骤的神秘面纱,并解释生成出来的工件的用途,方便大家将这个流程集成到自己的构建环境中。


关于工作流程,请注意: 在构建要发布的应用时,您可以直接使用 Flutter 工具,这样会简化构建流程。但也有一部分开发者可能会发现这个流程的可配置性不太理想,或是不适用于他们的构建设置或持续集成 (CI) 设置。


如果您使用自定义的 Xcode 或 Gradle 设置,那 Flutter 工具带来的所有开箱即用的便利都是可选的,您完全可以尽情调整,以让它适合自己的工作流程。


本文中的所有信息均适用于准备发布到 App Store 的 iOS 应用包。也就是说,项目是根据 Flutter 的发布模式 (Release Mode) 构建的。编译为 Debug 或 Profile 模式会使用不同的运行时和打包模型,以便支持热重装观测工具


  • 热重载
    https://flutter.io/hot-reload/
  • 观测工具
    https://dart-lang.github.io/observatory/get-started.html

Flutter 应用会将所有的用户界面在原生视图层级结构中渲染为单一的视图,如下图:

△ 在 Xcode 中看到的 Flutter 应用视图层级结构



应用包


使用 flutter build ios --release 命令创建 (或使用 IDE 直接创建) 的应用包 (application bundle) 看起来和典型的 iOS 应用包是相似的,该代码包内含应用可执行文件,以及所有引用到的框架和资源。


比如 Flutter 为 Runner 这个应用生成的代码包结构如下:



编译应用


您在编译应用的发布版本 (Release 版本,注意和 Profile 和 Debug 不同) 时,需要来自 buildbots 和主机的工件。(说到 buildbots,引擎部分使用的是 GN 和 Ninja,这里就不展开细说了,如果想要贡献代码至 Flutter,请查看我们的官方指引文档。)


  • 了解 Flutter 的构建模式

    https://github.com/flutter/flutter/wiki/Flutter's-modes

  • 了解 buildbots

    https://build.chromium.org/p/client.flutter/waterfall

  • 贡献代码至 Flutter

    https://github.com/flutter/engine/blob/master/CONTRIBUTING.md


安装 SDK 时,Flutter 工具会缓存在您的电脑上。您可以在 bin/cache 目录里查看它们。如果您决定将构建过程的任何步骤集成到自己的构建系统中,这个目录里包含了使用 Flutter 所需的所有工具。


我们接下来花几节文字来说明一下 Flutter iOS 应用包中特有的一些文件。



Flutter Engine 框架包


Flutter.framework 目录被打包为 iOS framework bundle,其中包括:
  • Flutter 引擎: 内含核心库 (如图形、文件和网络 I/O、可访问性支持、插件体系架构)、DartVM 和 Skia 渲染器。
  • Flutter 引擎引用的资源: 目前仅含 ICU 数据。


Flutter 引擎的 framework bundle 由 buildbots 生成,然后 Flutter 工具将其下载并缓存在电脑本地。


  • Flutter 引擎

    https://github.com/flutter/engine



AOT 框架包


App.framework 包内含所有由用户编写的 Dart 应用代码的 AOT (Ahead-Of-Time) 快照,以及为 Flutter 框架和插件编写的 Dart 代码,代码格式为 armv7s 和 aarch64。


在编译发布版本时,编译器会对 Dart 代码进行筛选,只有实际使用到的代码才会被打包。您的电脑上缓存的 gen_snapshot 工具会生成创建 App.framework 代码包所需的工件。


AOT 快照
前面提到的 AOT 快照库里包含从 Dart 编译出来的 AOT 原生机器码。


由 gen_snapshot 生成的快照库包含了四个主要标识。这些标识可以通过 nm 命令拿掉。例如:
$ nm -gU Runner.app/Frameworks/App.framework/AppRunner.app/Frameworks/App.framework/App (for architecture armv7):003c6f60 S _kDartIsolateSnapshotData00007000 T _kDartIsolateSnapshotInstructions003c16a0 S _kDartVmSnapshotData00004000 T _kDartVmSnapshotInstructions
Runner.app/Frameworks/App.framework/App (for architecture arm64):00000000004041a0 S _kDartIsolateSnapshotData0000000000009000 T _kDartIsolateSnapshotInstructions00000000003fc740 S _kDartVmSnapshotData0000000000005000 T _kDartVmSnapshotInstructions

上述 4 个标识的具体说明如下:

  • Dart VM 快照 (kDartVmSnapshotData): 代表 isolate 之间共享的 Dart 堆 (heap) 的初始状态。有助于更快地启动 Dart isolate,但不包含任何 isolate 专属的信息。

  • Dart VM 指令 (kDartVmSnapshotInstructions): 包含 VM 中所有 Dart isolate 之间共享的通用例程的 AOT 指令。这种快照的体积通常非常小,并且大多会包含程序桩 (stub)。

  • Isolate 快照 (kDartIsolateSnapshotData): 代表 Dart 堆的初始状态,并包含 isolate 专属的信息。

  • Isolate 指令 (kDartIsolateSnapshotInstructions): 包含由 Dart isolate 执行的 AOT 代码。


调用 gen_snapshot 很简单。只需将其指向 Dart 源代码,它就会为这四个标识中的每一个提供 Blob。然后,Xcode 将这些打包到 iOS framework bundle 中,这个过程和使用 C、C++、Objective-C 或 Swift 编写的框架一样。


  • 进一步了解如何配置快照和引擎
    https://github.com/flutter/engine/wiki/Flutter-Engine-Operation-in-AOT-Mode#snapshot-generation

除了打包代码之外,Flutter 工具还会读取项目 pubspec.yaml 文件中所列出的资源,从而确保应用 (及其使用的插件) 所引用的资源也会被收录进应用包中。



关于 Android 的一些说明


构建 Android APK 包的过程 (使用 flutter build apk --release 或直接使用 IDE 生成) 会产生如下文件结构:

它与 iOS 包几乎相同,但存在以下差异:
  • Flutter 引擎打包为 ELF 库 (libflutter.so)。
  • 前文中介绍的 4 个标识现在只是 Assets 目录中的二进制 Blob。


第二点可能有点出乎您的意料,这里解释一下: 您无需下载 NDK 即可构建发行版 APK。这是因为,在您的计算机上没有 NDK 的情况下,Flutter 工具会将 Blob 作为资源打包。在 Android 中,Flutter 引擎可以将页面标记为可执行文件。因此,当它检测到 AOT 资源被打包为二进制 Blob 时,它就会将这些 Blob 映射到内存中,并将相应的页面标记为可执行。如果您可以访问计算机上的 NDK,则可以指定其位置,并使用前述的标识生成动态库。当然,在这种情况下,Flutter 引擎就会转而使用动态库中的 4 个标识内容了。

  • 进一步了解 Flutter 引擎的 Android 配置细节
    https://github.com/flutter/engine/wiki/Flutter-Engine-Operation-in-AOT-Mode#android-configuration



总结


构建 iOS app bundle 的关键点如下:
  • 您可以将 Flutter 视图放置在本地视图层级结构中的任何位置。Flutter 渲染出来的所有内容都会合并到此视图中。
  • Dart 代码像其他任何 C++ / Objective-C / Swift 代码库一样,都会被编译为原生机器码,并打包为库或 framework bundle。这意味着所有崩溃报告和标识工具都会以相同的方式作用于 Dart AOT 代码。
  • 您可以将 Flutter 集成到自己的构建系统中,而不必依赖开发设备上的 Flutter 工具 (尽管使用这些工具可以让您的工作更轻松些)。所有工具都可以在 Flutter SDK 的 bin/cache 目录中找到。


您在使用 Flutter 构建 iOS 应用时有遇到过什么样的问题或挑战?欢迎在评论区和我们分享。



 点击屏末 | 阅读原文 | 进入 Flutter 开发者社区中文资源


  想了解更多 Flutter 内容?


  • 在公众号首页发送关键词 "Flutter",获取相关历史技术文章;

  • 还有更多疑惑?欢迎点击菜单 "联系我们" 反馈您在开发过程中遇到的问题。

推荐阅读




    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存