Flutter混合开发:在已有iOS项目中引入Flutter
前言:
这里不讲怎么搭建Flutter环境,请自行Google,这里只讲在已有iOS项目中引入Flutter。
目前混合开发属于主流,因为多数都在原来的项目上集成Flutter模块,除非新的项目用纯Flutter。
想要在已有的原生 App 里嵌入一些 Flutter 页面,有两个办法:
-
将原生工程作为 Flutter 工程的子工程,由 Flutter 统一管理。这种模式,就是统一管理模式。
-
将 Flutter 工程作为原生工程共用的子模块,维持原有的原生工程管理方式不变。这种模式,就是三端分离模式。
image
三端代码分离的模式来进行依赖治理,实现了 Flutter 工程的轻量级接入,三端代码分离模式把 Flutter 模块作为原生工程的子模块,还可以快速实现 Flutter 功能的“热插拔”,降低原生工程的改造成本。而 Flutter 工程通过 Android Studio 进行管理,无需打开原生工程,可直接进行 Dart 代码和原生代码的开发调试;
三端工程分离模式的关键是抽离 Flutter 工程,将不同平台的构建产物依照标准组件化的形式进行管理,即 Android 使用 aar、iOS 使用 pod。换句话说,接下来介绍的混编方案会将 Flutter 模块打包成 aar 和 pod,这样原生工程就可以像引用其他第三方原生组件库那样快速接入 Flutter 了。
集成(以iOS为例),使用Pods方式
官方给出了三种接入方案,这三种方案各有优缺点,我们先简单看看这三种方案:
- 使用 CocoaPods 和 Flutter SDK 集成:ios项目中用CocoaPods直接接入管理flutter module。这种方案需要所有开发人员都配置flutter环境,且安装CocoaPods;优点是通过CocoaPods自动集成,配置简单。
- 在 Xcode 中集成 frameworks:将flutter module先build成FrameWork文件,然后在ios项目中引入文件。这种方案的优点是ios开发人员不需要flutter环境,且项目不需要安装CocoaPods;缺点是每次修改都需要重新build,重新导入。
- 通过CocoaPods打包Framework:与2类似,只不过在build时加入--cocoapods参数:flutter build ios-framework --cocoapods --xcframework --no-universal --output=some/path/MyApp/Flutter/。打包出来的是Flutter.podspec 文件,ios项目中通过CocoaPods管理集成。这个方案的与2方案差不多,缺点也是每次改动需要重新build,优点是ios开发人员不需要flutter环境。
所以要根据自身的情况来选择符合自己的方案。官方推荐第一种方案,我也先尝试了第一个方案。
方案一(CocoaPods直接接入管理flutter module)
首先我们在现有工程目录创建一个flutter module的项目,可以用命令创建
flutter create -t module flutter_module
- 注:
flutter create flutter_module
和flutter create -t module flutter_module
区别在于带module参数,创建出来的工程会把辅助用的android,ios工程给隐藏掉,要涉及打包以及修改配置,可以不带module参数创建。
这里的 Flutter 模块,也是 Flutter 工程,我们用 Android Studio或vs code 打开它:
image.png
在现有工程中找到Podfile,添加如下配置:
此处省略...
# my_flutter 是创建Flutter的模块名称
flutter_application_path = './flutter_module'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
platform :ios, '13.0'
target 'FlutterDemo' do
# use_frameworks!
# 这边引入flutter
install_all_flutter_pods(flutter_application_path)
此处省略...
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
xcconfig_path = config.base_configuration_reference.real_path
xcconfig = File.read(xcconfig_path)
xcconfig_mod = xcconfig.gsub(/DT_TOOLCHAIN_DIR/, "TOOLCHAIN_DIR")
File.open(xcconfig_path, "w") { |file| file << xcconfig_mod }
end
end
# 防止flutter sdk使用的是最新3.x版本 pod install报错 Missing `flutter_post_install(installer)`
flutter_post_install(installer) if defined?(flutter_post_install)
end
由于xcode15之后会报错error: DT_TOOLCHAIN_DIR cannot be used to evaluate LIBRARY_SEARCH_PATHS, use TOOLCHAIN_DIR instead (in target 'MyFlutter' from project 'Pods')
所有需要在末尾加上post_install
相关配置。
然后在原生项目下 执行 pod install 如果以上不报错,混合开发模式到这里就集成完了。
集成后编译不通过,报错:framework not found FlutterPluginRegistrant
。但是我们并没有使用任何flutter plugin,所以不存在这个文件,但是CocoaPods不知道为什么一定要这个文件,所以导致一直编译失败,加上这个方案入侵有点大,所以放弃了这种集成方案。
方案二(在 Xcode 中集成 frameworks)
在 iOS 平台,原生工程对 Flutter 的依赖分别是:
- Flutter 库和引擎,即 Flutter.framework;
- Flutter 工程的产物,即 App.framework;
- Flutter 资源文件,即Assets.car;
iOS 平台的 Flutter 模块抽取,实际上就是通过打包命令生成这两个产物,并将它们封装成一个 pod 供原生工程引用。
接下来,我们要做的事情就是把这段代码编译打包,构建出对应的 Android 和 iOS 依赖库,实现原生工程的接入,命令如下:
flutter build ios
这里就会出现一个问题:签名问题。执行上面命令后会报错,这里可以在build的时候选择不签名,命令如下:
flutter build ios --no-codesign
这样就可以build成功,默认编译的是release包,加上--debug
可以编译debug包。
也可以使用下面的命令:
flutter build ios-framework --cocoapods --xcframework --no-universal --output=build/archive/ios
然后在ios项目中直接将Flutter.xcframework和App.xcframework等文件引入工程。
然后修改原生代码,启动 FlutterEngine
和 FlutterViewController
,如下:
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.flutterEngine = [[FlutterEngine alloc] initWithName:@"my flutter engine"];
[self.flutterEngine run];
[GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
return YES;
}
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
FlutterViewController *flutterViewController =
[[FlutterViewController alloc] initWithEngine:appDelegate.flutterEngine nibName:nil bundle:nil];
flutterViewController.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:flutterViewController animated:YES completion:nil];
}
注:确保引擎启动完毕后,再调用FlutterViewController,否则flutter页面展示失败。
然后编译工程报错如下:
iOS Xcode 15 Sandbox: rsync(xxxx) deny(1) file-write-create
解决方案:设置里面搜索user 把User Script Sanboxing 改为NO。
最后点击运行,Flutter Widget 页面展示出来了。
方案三(通过CocoaPods打包Framework)
修改工程下的Podfile:
pod "Flutter", :path => "./LocalPods/Flutter"
然后和方案二一样修改原生代码,启动 FlutterEngine
和 FlutterViewController