使用 Flutter 从头开始写一个 App,是一件轻松惬意的事情。但,对于成熟产品来说,完全摒弃原有 App 的历史沉淀,而全面转向 Flutter 并不现实。用 Flutter 去统一 iOS/Android 技术栈,把它作为已有原生 App 的扩展能力,通过逐步试验有序推进从而提升终端开发效率,可能才是现阶段 Flutter 最具吸引力的地方。
那么,Flutter 工程与原生工程该如何组织管理?不同平台的 Flutter 工程打包构建产物该如何抽取封装?封装后的产物该如何引入原生工程?原生工程又该如何使用封装后的 Flutter 能力?
这些问题使得在已有原生 App 中接入 Flutter 看似并不是一件容易的事情。那接下来,我就和你介绍下如何在原生 App 中以最自然的方式接入 Flutter。
准备工作
既然是要在原生应用中混编 Flutter,相信你一定已经准备好原生应用工程来实施今天的改造了。如果你还没有准备好也没关系,我会以一个最小化的示例和你演示这个改造过程。
首先,我们分别用 Xcode 与 Android Studio 快速建立一个只有首页的基本工程,工程名分别为 iOSDemo 与 AndroidDemo。
对于 iOS 工程来说,由于基本工程并不支持以组件化的方式管理项目,因此我们还需要多做一步,将其改造成使用 CocoaPods 管理的工程,也就是要在 iOSDemo 根目录下创建一个只有基本信息的 Podfile 文件:
use_frameworks!
platform :ios, '8.0'
target 'iOSDemo' do
#todo
end
然后,在命令行输入 pod install 后,会自动生成一个 iOSDemo.xcworkspace 文件,这时我们就完成了 iOS 工程改造。
Flutter 混编方案介绍
如果你想要在已有的原生 App 里嵌入一些 Flutter 页面,有两个办法:
- 将原生工程作为 Flutter 工程的子工程,由 Flutter 统一管理。这种模式,就是统一管理模式
- 将 Flutter 工程作为原生工程共用的子模块,维持原有的原生工程管理方式不变。这种模式,就是三端分离模式
由于 Flutter 早期提供的混编方式能力及相关资料有限,国内较早使用 Flutter 混合开发的团队大多使用的是统一管理模式。但是,随着功能迭代的深入,这种方案的弊端也随之显露,不仅三端(Android、iOS、Flutter)代码耦合严重,相关工具链耗时也随之大幅增长,导致开发效率降低。
所以,后续使用 Flutter 混合开发的团队陆续按照三端代码分离的模式来进行依赖治理,实现了 Flutter 工程的轻量级接入。
除了可以轻量级接入,三端代码分离模式把 Flutter 模块作为原生工程的子模块,还可以快速实现 Flutter 功能的“热插拔”,降低原生工程的改造成本。而 Flutter 工程通过 Android Studio 进行管理,无需打开原生工程,可直接进行 Dart 代码和原生代码的开发调试。
三端工程分离模式的关键是抽离 Flutter 工程,将不同平台的构建产物依照标准组件化的形式进行管理,即 Android 使用 aar、iOS 使用 pod。换句话说,接下来介绍的混编方案会将 Flutter 模块打包成 aar 和 pod,这样原生工程就可以像引用其他第三方原生组件库那样快速接入 Flutter 了。
集成 Flutter
我曾在前面的文章中提到,Flutter 的工程结构比较特殊,包括 Flutter 工程和原生工程的目录(即 iOS 和 Android 两个目录)。在这种情况下,原生工程就会依赖于 Flutter 相关的库和资源,从而无法脱离父目录进行独立构建和运行。
- Flutter 库和引擎,也就是 Flutter 的 Framework 库和引擎库;
- Flutter 工程,也就是我们自己实现的 Flutter 模块功能,主要包括 Flutter 工程 lib 目录下的 Dart 代码实现的这部分功能。
将Flutter模块集成到本机iOS的最简单选择是使用CocoaPods。
在已经有原生工程的情况下,我们需要在同级目录创建 Flutter 模块,构建 iOS 和 Android 各自的 Flutter 依赖库。这也很好实现,Flutter 就为我们提供了这样的命令。我们只需要在原生项目的同级目录下,执行 Flutter 命令创建名为 my_flutter 的模块即可:
cd ~/Desktop/FlutterDemo
flutter create --template module my_flutter
my_flutter文件夹下的很多文件默认是隐藏状态的,我们可以使用快捷键 ⌘⇧.(Command + Shift + .) 来快速(在 Finder 中)显示和隐藏隐藏文件。
将Flutter模块集成到本机iOS的最简单选择是使用CocoaPods。如果您已经在iOS项目中创建了Podfile,则可以跳过这一步。
- 在iOS项目目录运行以下命令:
pod init
- 创建Podfile之后,整个项目结构应如下所示:
some/path/
├── my_flutter/
│ └── .iOS/
│ └── Flutter/
│ └── podhelper.rb
└── MyApp/
└── Podfile
- 打开Podfile ,将以下几行添加到您的Podfile:
flutter_application_path = '../my_flutter'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
4.对于每个需要嵌入Flutter的,请调用install_all_flutter_pods(flutter_application_path)
。
target 'MyApp' do
install_all_flutter_pods(flutter_application_path)
end
5.保存并关闭Podfile 运行以下命令来安装Pods
pod install
然后使用带有.xcworkspace扩展名的文件重新打开项目。
该podhelper.rb
脚本嵌入你的插件, Flutter.framework
以及App.framework
到项目中。
您应用的Debug和Release构建配置分别嵌入了Flutter的Debug或Release构建模式。将配置文件构建配置添加到您的应用,以在配置文件模式下进行测试。
iOS 模块集成
iOS 工程接入的情况要稍微复杂一些。在 iOS 平台,原生工程对 Flutter 的依赖分别是:
- Flutter 库和引擎,即 Flutter.framework;
- Flutter 工程的产物,即 App.framework。
iOS 平台的 Flutter 模块抽取,实际上就是通过打包命令生成这两个产物,并将它们封装成一个 pod 供原生工程引用。
类似地,首先我们在 my_flutter 的根目录下,执行 iOS 打包构建命令:
flutter build ios --debug
这条命令的作用是编译 Flutter 工程生成两个产物:Flutter.framework 和 App.framework。同样,把 debug 换成 release 就可以构建 release 产物(当然,你还需要处理一下签名问题)
其次,在 MyApp 的根目录下创建一个名为 FlutterEngine 的目录,并把这两个 framework 文件拷贝进去。我们需要把这两个产物手动封装成 pod。因此,我们还需要在该目录下创建 FlutterEngine.podspec,即 Flutter 模块的组件定义:
Pod::Spec.new do |s|
s.name = 'FlutterEngine'
s.version = '0.1.0'
s.summary = 'XXXXXXX'
s.description = <<-DESC
TODO: Add long description of the pod here.
DESC
s.homepage = 'https://github.com/xx/FlutterEngine'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'chenhang' => 'hangisnice@gmail.com' }
s.source = { :git => "", :tag => "#{s.version}" }
s.ios.deployment_target = '8.0'
s.ios.vendored_frameworks = 'App.framework', 'Flutter.framework'
end
target 'iOSDemo' do
pod 'MyApp', :path => '../'
end
pod install 一下,Flutter 模块就集成进 iOS 原生工程中了。
或者我们直接把FlutterEngine模块组件作为一个私有库组件,my_flutter项目负责flutter相关项目的开发,然后FlutterEngine模块直接用脚本自动打包成相应的Flutter 库。
相关配置参考将Flutter模块集成到您的iOS项目中