将 Flutter 集成到现有应用

1,315 阅读7分钟

有时候,用 Flutter 一次性重写整个已有的应用是不切实际的。对于这些情况,Flutter 可以作为一个库或模块,集成进现有的应用当中。模块引入到您的 Android 或 iOS 应用(当前支持的平台)中,以使用 Flutter 来渲染一部分的 UI,或者仅运行多平台共享的 Dart 代码逻辑。

仅需几步,你就可以将高效而富有表现力的 Flutter 引入您的应用。

在 Flutter v1.12 中,添加到现有应用的基本场景已被支持,每个应用在同一时间可以集成一个全屏幕的 Flutter 实例。目前仍有以下限制:

  • 目前尚未支持将多个 Flutter 库打包到同一个应用中;
  • 在 Android 平台,使用了添加到现有应用 (add-to-app) 的插件需要迁移到基于 FlutterPlugin 的 Android 插件 API
  • 一些不支持 FlutterPlugin 的插件可能会有不可预知的行为,比如产生错误的预判,认为 Flutter Activity 一直处于活跃状态。
  • 从 1.17 开始,Flutter 模块仅支持 Android 平台中的 AndroidX 应用。

自 Flutter 1.26 版本开始,add-to-app 开始实验性的支持将多个 Flutter 引擎 (engine)、页面 (screen) 或视图 (view) 添加到你的应用中。适用于混合栈应用在导航到原生页面和 Flutter 页面的情况,也适用于一个页面有原生视图和 Flutter 视图的情况等混合栈应用。多个 Flutter 实例会帮助每个实例保持独立的应用和 UI 状态,同时使用最少的内存资源。请多详细内容,请参考文档: 多个 Flutter 实例

集成到 iOS 应用

  • 在 Xcode 的 Build Phase 以及 CocoaPods 中,添加一个自动构建并引入 Flutter 模块的 Flutter SDK 钩子。
  • 将 Flutter 模块构建为通用的 iOS Framework 以便集成到您自己的构建系统中;
  • FlutterEngine API 用于启动并持续地为挂载 FlutterViewController 以提供独立的 Flutter 环境;
  • 支持了 Objective-C 和 Swift 为宿主的应用程序;
  • Flutter 模块可以通过使用 Flutter plugins 与平台进行交互;
  • 支持通过从 IDE 或命令行中使用 flutter attach 来实现 Flutter 调试与有状态的热重载。

查看 add-to-app GitHub 示例仓库 中在 iOS 和 Android 平台上引入 Flutter module 的示例项目。

Flutter 可以以 framework 框架的形式添加到你的既有 iOS 应用中。

请参阅 add_to_app 代码示例 的 iOS 目录。

创建 Flutter module

为了将 Flutter 集成到你的既有应用里,第一步要创建一个 Flutter module。

在命令行中执行:

cd some/path/
flutter create --template module my_flutter

Flutter module 会创建在 some/path/my_flutter/ 目录。在这个目录中,你可以像在其它 Flutter 项目中一样,执行 flutter 命令。比如 flutter run --debug 或者 flutter build ios

模块组织

在 my_flutter 模块,目录结构和普通 Flutter 应用类似:

my_flutter/
├── .ios/
│   ├── Runner.xcworkspace
│   └── Flutter/podhelper.rb
├── lib/
│   └── main.dart
├── test/
└── pubspec.yaml

添加你的 Dart 代码到 lib/ 目录。

添加 Flutter 依赖到 my_flutter/pubspec.yaml,包括 Flutter packages 和 plugins。

.ios/ 隐藏文件夹包含了一个 Xcode workspace,用于单独运行你的 Flutter module。它是一个独立启动 Flutter 代码的壳工程,并且包含了一个帮助脚本,用于编译 framewroks 或者使用 CocoaPods 将 Flutter module 集成到你的既有应用。

提示

iOS 代码要添加到你的既有应用或者 Flutter plugin中,而不是 Flutter module 的 .ios/ 目录下。 .ios/ 下的改变不会集成到你的既有应用,并且这有可能被 Flutter 重写。

由于 .ios/ 目录是自动生成的,因此请勿对其进行版本控制。在新机器上构建 module 时,请在使用 Flutter module 构建 iOS 项目之前,先于 my_flutter 目录运行 flutter pub get 以生成 .ios/ 目录。

在你的既有应用中集成 Flutter module

这里有两种方式可以将 Flutter 集成到你的既有应用中。

  1. 使用 CocoaPods 依赖管理和已安装的 Flutter SDK 。(推荐)
  2. 把 Flutter engine 、你的 dart 代码和所有 Flutter plugin 编译成 framework 。用 Xcode 手动集成到你的应用中,并更新编译设置。

提示

你的应用将不能在模拟器上运行 Release 模式,因为 Flutter 还不支持将 Dart 代码编译成 x86/x86_64 ahead-of-time (AOT) 模式的二进制文件。你可以在模拟机和真机上运行 Debug 模式,在真机上运行 Release 模式。

使用 Flutter 会 增加应用体积 。

选项 A - 使用 CocoaPods 和 Flutter SDK 集成

这个方法需要你的项目的所有开发者,都在本地安装 Flutter SDK。只需要在 Xcode 中编译应用,就可以自动运行脚本来集成 dart 代码和 plugin。这个方法允许你使用 Flutter module 中的最新代码快速迭代开发,而无需在 Xcode 以外执行额外的命令。

下面的示例假设你的既有应用和 Flutter module 在相邻目录。如果你有不同的目录结构,需要适配到对应的路径。

some/path/
├── my_flutter/
│   └── .ios/
│       └── Flutter/
│         └── podhelper.rb
└── MyApp/
    └── Podfile

如果你的应用(MyApp)还没有 Podfile,根据 CocoaPods getting started guide 来在项目中添加 Podfile

  1. 在 Podfile 中添加下面代码:

    flutter_application_path = '../my_flutter'
    load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
    
  2. 每个需要集成 Flutter 的 Podfile target,执行 install_all_flutter_pods(flutter_application_path)

    target 'MyApp' do
      install_all_flutter_pods(flutter_application_path)
    end
    
  3. 运行 pod install

     提示

    当你在 my_flutter/pubspec.yaml 改变了 Flutter plugin 依赖,需要在 Flutter module 目录运行 flutter pub get,来更新会被podhelper.rb 脚本用到的 plugin 列表,然后再次在你的应用目录 some/path/MyApp 运行 pod install.

podhelper.rb 脚本会把你的 plugins, Flutter.framework,和 App.framework 集成到你的项目中。

你应用的 Debug 和 Release 编译配置,将会集成相对应的 Debug 或 Release 的 编译产物。可以增加一个 Profile 编译配置用于在 profile 模式下测试应用。

小提示

Flutter.framework 是 Flutter engine 的框架, App.framework 是你的 Dart 代码的编译产物。

在 Xcode 中打开 MyApp.xcworkspace ,你现在可以使用 ⌘B 编译项目了。

选项 B - 在 Xcode 中集成 frameworks

除了上面的方法,你也可以创建必备的 frameworks,手动修改既有 Xcode 项目,将他们集成进去。当你组内其它成员们不能在本地安装 Flutter SDK 和 CocoaPods,或者你不想使用 CocoaPods 作为既有应用的依赖管理时,这种方法会比较合适。但是每当你在 Flutter module 中改变了代码,都必须运行 flutter build ios-framework

如果你使用前面的 使用 CocoaPods 和 Flutter SDK 集成,你可以跳过本步骤。

下面的示例假设你想在 some/path/MyApp/Flutter/ 目录下创建 frameworks:

flutter build ios-framework --output=some/path/MyApp/Flutter/
some/path/MyApp/
└── Flutter/
    ├── Debug/
    │   ├── Flutter.xcframework
    │   ├── App.xcframework
    │   ├── FlutterPluginRegistrant.xcframework (only if you have plugins with iOS platform code)
    │   └── example_plugin.xcframework (each plugin is a separate framework)
    ├── Profile/
    │   ├── Flutter.xcframework
    │   ├── App.xcframework
    │   ├── FlutterPluginRegistrant.xcframework
    │   └── example_plugin.xcframework
    └── Release/
        ├── Flutter.xcframework
        ├── App.xcframework
        ├── FlutterPluginRegistrant.xcframework
        └── example_plugin.xcframework

请注意

始终使用相同目录下的 Flutter.framework 和 App.framework。混合使用不同目录(例如 Profile/Flutter.framework 以及 Debug/App.framework)将会导致运行失败。

小提示

在 Xcode 11 中,你可以添加 --xcframework --no-universal 参数来生成 XCFrameworks,而不是使用通用的 framework。

在 Xcode 中将生成的 frameworks 集成到你的既有应用中。例如,你可以在 some/path/MyApp/Flutter/Release/ 目录拖拽 frameworks 到你的应用 target 编译设置的 General > Frameworks, Libraries, and Embedded Content 下,然后在 Embed 下拉列表中选择 “Embed & Sign”。

链接到框架

例如,你可以将框架从 Finder 的 some/path/MyApp/Flutter/Release/ 拖到你的目标项目中,然后点击以下步骤 build settings > Build Phases > Link Binary With Libraries。

在 target 的编译设置中的 Framework Search Paths (FRAMEWORK_SEARCH_PATHS) 增加 $(PROJECT_DIR)/Flutter/Release/

Update Framework Search Paths in Xcode

Embed the frameworks

内嵌框架

生成的动态框架必须嵌入你的应用并且在运行时被加载。

重点提醒

插件会帮助你生成 静态或动态框架。静态框架应该直接链接而不是嵌入。如果你在应用中嵌入了静态框架,你的应用将不能发布到 App Store 并且会得到一个 Found an unexpected Mach-O header code 的 archive error。

例如,你可以从应用框架组中拖拽框架(除了 FlutterPluginRegistrant 以及其他的静态框架)到你的目标 ‘ build settings > Build Phases > Embed Frameworks。然后从下拉菜单中选择 “Embed & Sign”。

Embed frameworks in Xcode

你现在可以在 Xcode中使用 ⌘B 编译项目。

 小提示

如果你想在 Debug 编译配置下使用 Debug 版本的 Flutter frameworks,在 Release 编译配置下使用 Release 版本的 Flutter frameworks,在 MyApp.xcodeproj/project.pbxproj 文件中,尝试在所有 Flutter 相关 frameworks 上使用 path = "Flutter/$(CONFIGURATION)/example.framework"; 替换 path = Flutter/Release/example.framework; (注意添加引号 ")。

你也必须在 Framework Search Paths (FRAMEWORK_SEARCH_PATHS) 编译设置中使用 $(PROJECT_DIR)/Flutter/$(CONFIGURATION)

选项 C - 使用 CocoaPods 在 Xcode 和 Flutter 框架中内嵌应用和插件框架

除了将一个很大的 Flutter.framework 分发给其他开发者、机器或者持续集成 (CI) 系统之外,你可以加入一个参数 --cocoapods 将 Flutter 框架作为一个 CocoaPods 的 podspec 文件分发。这将会生成一个 Flutter.podspec 文件而不再生成 Flutter.framework 引擎文件。如选项 B 中所说的那样,它将会生成 App.framework 和插件框架。

flutter build ios-framework --cocoapods --output=some/path/MyApp/Flutter/

content_copy

some/path/MyApp/
└── Flutter/
    ├── Debug/
    │   ├── Flutter.podspec
    │   ├── App.xcframework
    │   ├── FlutterPluginRegistrant.xcframework
    │   └── example_plugin.xcframework (each plugin with iOS platform code is a separate framework)
    ├── Profile/
    │   ├── Flutter.podspec
    │   ├── App.xcframework
    │   ├── FlutterPluginRegistrant.xcframework
    │   └── example_plugin.xcframework
    └── Release/
        ├── Flutter.podspec
        ├── App.xcframework
        ├── FlutterPluginRegistrant.xcframework
        └── example_plugin.xcframework

Host apps using CocoaPods can add Flutter to their Podfile:

pod 'Flutter', :podspec => 'some/path/MyApp/Flutter/[build mode]/Flutter.podspec'