本文主要针对现有iOS项目想接入flutter,怎么接入flutter,如何进行项目管理,以及Native和flutter之间如何调用,如何调试来讲解的。
一、创建Flutter Module 执行下面的命令创建Flutter Moudle
1 2 cd some/path/ flutter create --template module my_flutter
some/path/
是你要存放工程的目录,然后创建flutter Module,这一步要注意,不要创建成flutter project项目了,执行命令后,控制台会打印:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Creating project my_flutter... androidx: true my_flutter/test/widget_test.dart (created) my_flutter/my_flutter.iml (created) my_flutter/.gitignore (created) my_flutter/.metadata (created) my_flutter/pubspec.yaml (created) my_flutter/README.md (created) my_flutter/lib/main.dart (created) my_flutter/my_flutter_android.iml (created) my_flutter/.idea/libraries/Flutter_for_Android.xml (created) my_flutter/.idea/libraries/Dart_SDK.xml (created) my_flutter/.idea/modules.xml (created) my_flutter/.idea/workspace.xml (created) Running "flutter pub get" in my_flutter... 1.8s Wrote 12 files. All done ! Your module code is in my_flutter/lib/main.dart.
创建完成以后my_flutter文件结构如下:
1 2 3 4 5 6 7 8 my_flutter/ ├── .ios/ │ ├── Runner.xcworkspace │ └── Flutter/podhelper.rb ├── lib/ │ └── main.dart ├── test / └── pubspec.yaml
接下来可以在lib中添加代码逻辑,在pubspec.yaml中,添加依赖的packages和plugins。
二、集成方式 1.使用CocoaPods和Flutter SDK集成 这个方案是针对高于Flutter 1.8.4-pre.21
版本的SDK的混编方案,如果使用之前的SDK,请查看Upgrading Flutter added to existing iOS Xcode project 和Add Flutter to existing apps
1.1 Podfile 中添加下面配置 1 2 flutter_application_path = '../my_flutter' load File.join (flutter_application_path, '.ios' , 'Flutter' , 'podhelper.rb' )
../my_flutter
是你flutter Moudle存放的目录,这里my_flutter存放在profile的上一级目录,所以这么写。
1.2 Podfile target 中添加install_all_flutter_pods(flutter_application_path) 1 2 3 target 'MyApp' do install_all_flutter_pods(flutter_application_path) end
这里的MyApp就是对应iOS项目的名称,存放到自己项目对应target中就好了。
1.3 pod install 在Podfile所在目录,执行pod install,如果没问题,会在你的项目中增加以下依赖:
1 2 3 Installing Flutter (1.0 .0 ) Installing FlutterPluginRegistrant (0.0 .1 ) Installing my_flutter (0.0 .1 )
在执行pod install以后,如果没有增加上面👆的依赖,那么可能是工程有问题。
问题1.Profile中路径添加错误或者my_flutter是Flutter project,不是Flutter Moudle 提示错误如下:
1 2 3 4 5 6 7 8 [!] Invalid `Podfile` file: cannot load such file -- ./my_flutter/.ios/Flutter/podhelper.rb. # from /Users/Example/Podfile:10 # ------------------------------------------- # > load File.join (flutter_application_path, '.ios' , 'Flutter' , 'podhelper.rb' ) # # -------------------------------------------
这时候要检查下flutter_application_path
是否正确,如果正确,查看下my_flutter是否是Flutter project,可以查看下my_flutter
是否包含,.iOS
这个文件,注意这是一个隐藏文件,先要在电脑中设置成显示隐藏你文件,再进行查看确认,如果包含则是Moudle,不包含则是project。
问题2.项目签名不对
在my_flutter目录下运行如下命令:
1 2 open -a Simulatorflutter build ios
查看能否正确运行,如果提示以下错误,则是证书问题:
1 2 3 4 It appears that your application still contains the default signing identifier. Try replacing 'com.example' with your signing id in Xcode: open ios/Runner.xcworkspace Encountered error while building for device.
怎么解决这个问题?
方法一:
1.找到my_flutter/.ios,打开Runner.xcworkspace文件
2.找到Signing & Capabilities,将Signing证书配置正确就可以了(这里要配置Generic iOS Deveice)
方法二: 使用如下命令,忽略签名:
1 flutter build ios --release --no-codesign
配置成功之后,再次运行flutter build ios
,会打印如下信息:
1 2 3 4 5 6 7 8 9 10 Automatically signing iOS for device deployment using specified development team in Xcode project: 56 XB5ELH9ARunning Xcode build... ├─Building Dart code... 78.1s ├─Generating dSYM file... 0.1s ├─Stripping debug symbols... 0.0s ├─Assembling Flutter resources... 0.9s └─Compiling, linking and signing... 2.9s Xcode build done. 84.0s Built
然后再次pod install应该就可以成功了。
1.4 方案优缺点 优点:
1.功能配置简单,方便管理。
2.使用CocoaPods便于集成。
缺点:
1.团队成员都必须配置flutter环境,否则编译不过
2.Native代码和Flutter代码存放在一起,会变得复杂。
2.framework方式接入 2.1生成FrameWork 首先切换到my_flutter所在目录,执行下列命令,生成framework
1 flutter build ios-framework --output=../Flutter/
命令执行成功后,会在my_flutter同一级目录下,产生Flutter的文件,文件结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Flutter/ ├── Debug/ │ ├── Flutter.framework │ ├── App.framework │ ├── FlutterPluginRegistrant.framework (only if you have plugins with iOS platform code) │ └── example_plugin.framework (each plugin is a separate framework) ├── Profile/ │ ├── Flutter.framework │ ├── App.framework │ ├── FlutterPluginRegistrant.framework │ └── example_plugin.framework └── Release/ ├── Flutter.framework ├── App.framework ├── FlutterPluginRegistrant.framework └── example_plugin.framework
2.2配置frameWork路径 在项目中找到这个路径build settings > Build Phases > Link Binary With Libraries
添加$(PROJECT_DIR)/Flutter/Release/
到Framework Search Paths
2.3嵌入frameWork 在项目中找到这个路径General > Frameworks,Libraries and Embedded Content
app.Framework
和Flutter.FrameWork
添加到项目中,就可以使用了。
问题1.Failed to find assets path for “flutter_assets”
1 2 Failed to find assets path for "flutter_assets" [VERBOSE-2:engine.cc(114) ] Engine run configuration was invalid.
如果报上面的错误,则在my_flutter中运行以下命令:
1 2 flutter clean flutter build ios
问题2.dyld: Library not loaded: @rpath/Flutter.framework/Flutter
这个问题是说明嵌入frameWork有问题,可以检查一下,Embed Framework和Link Binary With Libraries
。
2.4 方案优缺点 优点:
缺点:
3.使用Flutter framework和CocoaPods集成(本地) 3.1生成frameWork 在Flutter v1.13.6之后版本,支持–cocoapods参数,可以使用下面命令。
1 flutter build ios-framework --cocoapods --output=../Flutter/
生成如下文件结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Flutter/ ├── Debug/ │ ├── Flutter.podspec │ ├── App.framework │ ├── FlutterPluginRegistrant.framework │ └── example_plugin.framework (each plugin with iOS platform code is a separate framework) ├── Profile/ │ ├── Flutter.podspec │ ├── App.framework │ ├── FlutterPluginRegistrant.framework │ └── example_plugin.framework └── Release/ ├── Flutter.podspec ├── App.framework ├── FlutterPluginRegistrant.framework └── example_plugin.framework
3.2配置profile文件 1 pod 'Flutter' , :podspec => '../Flutter/{build_mode}/Flutter.podspec'
3.3 方案优缺点 优点:
1.团队成员不依赖flutter环境
2.可以使用cocoapods集成管理。
缺点:
1.Flutter版本有限制
2.每次需要自己打frmaework
4.使用Flutter framework和CocoaPods集成(远程) 4.1创建一个CocoaPods私有库 在my_flutter同级目录下,创建CocoaPods私有库
1 $ pod lib create MyFlutterFramework
终端执行代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 xingkunkun:FlutterForFW admin$ pod lib create MyFlutterFramework Cloning `https: Configuring MyFlutter template. ------------------------------ To get you started we need to ask a few questions, this should only take a minute. What platform do you want to use?? [ iOS / macOS ] > ios What language do you want to use?? [ Swift / ObjC ] > objc Would you like to include a demo application with your library? [ Yes / No ] > no Which testing frameworks will you use? [ Specta / Kiwi / None ] > none Would you like to do view based testing? [ Yes / No ] > no What is your class prefix ? > Running pod install on your new library .
4.2创建一个Flutter Module
1 flutter create --template module my_flutter
1 2 3 $ flutter build ios --debug 或者 flutter build ios --release --no-codesign(选择不需要证书)
3.检查.ios目录下
是否有Flutter–>App.framework
是否有Flutter–>engine–>Flutter.framework
1 2 3 .ios 目录下Flutter-->App.framework Flutter-->engine-->Flutter.framework
4.3将CocoaPods私有库集成到Native项目中 在MyFlutterFramework中创建ios_frameworks文件夹,并将App.framework
和Flutter.framework
拷贝进去。
在MyFlutterFramework的podspec文件中,添加以下配置:
1 2 3 4 s.static_framework = true arr = Array.new arr.push ('ios_frameworks/*.framework' ) s.ios.vendored_frameworks = arr
之后在MyFlutterFramework的podfile同级目录中执行
在MyApp工程下的podfile文件中添加
1 2 3 4 5 6 7 8 9 10 platform :ios , '8.0' target 'MyApp' do use_frameworks! pod 'MyFlutterFramework' , :path => '../MyFlutterFramework' end
之后在MyApp的podfile同级目录中执行
这时在MyApp中,就可以找到App.framework
和Flutter.framework
4.4将MyFlutterFramework和my_flutter推送到远程仓库
1.MyFlutterFramework和my_flutter推送到远程仓库
2.修改MyApp工程下的podfile,将pod 'MyFlutterFramework'
依赖修改为MyFlutterFramework远程连接。
1 2 3 4 5 6 7 8 9 10 platform :ios , '8.0' target 'MyApp' do use_frameworks! pod 'MyFlutterFramework' , :git=>'https://gitlab.com/MyFlutterFramework.git' end
如果MyFlutterFramework中的ios_frameworks不详推送到远程仓库,可以在gitignore文件中添加一下
4.5 方案优缺点 优点:
1.团队成员不依赖flutter环境
2.可以使用cocoapods集成管理。
3.可以使用远程仓库共享和管理项目代码
缺点:
1.每次重新构建,需要移动framework位置,比较繁琐,可以使用脚本解决。
三、Flutter与Native交互 Flutter 官方提供了一种 Platform Channel 的方案,用于 Dart 和平台之间相互通信。
核心原理:
Flutter应用通过Platform Channel将传递的数据编码成消息的形式,跨线程发送到该应用所在的宿主(Android或iOS);
宿主接收到Platform Channel的消息后,调用相应平台的API,也就是原生编程语言来执行相应方法;
执行完成后将结果数据通过同样方式原路返回给应用程序的Flutter部分。
Flutter提供了三种不同的Channel:
BasicMessageChannel(主要是传递字符串和一些半结构体的数据)
MethodChannel(用于传递方法调用)
EventChannel(数据流的通信)
下面是使用Platform Channel进行通信
1.Native app主动与Flutter交互 交互主要分为三步:
1.flutter注册MethodChannel
2.flutter MethodChannel监听native消息
3.native通过MethodChannel发送消息
Dart代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class HomePage extends StatefulWidget { _HomePageState createState() => _HomePageState(); } class _HomePageState extends State <HomePage > { String title = 'Flutter to Native' ; Color backGroundColor = Colors.red; static const MethodChannel methodChannel = const MethodChannel('com.pages.your/native_get' ); _HomePageState(){ methodChannel.setMethodCallHandler((MethodCall call){ if (call.method == "NativeToFlutter" ){ setState(() { title = call.arguments; backGroundColor = Colors.yellow; }); } return Future<dynamic >.value(); }); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: backGroundColor, body: Center( child: GestureDetector( behavior: HitTestBehavior.opaque, child: new Text(title), onTap: (){ _iOSPushToVC(); }, ), ), ); } }
Native代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #import "ViewController.h" #import <Flutter/Flutter.h> #define SCREEN_WIDTH ([[UIScreen mainScreen] bounds].size.width) @interface ViewController ()@property (nonatomic , strong ) FlutterMethodChannel *messageChannel;@end @implementation ViewController - (void )viewDidLoad { [super viewDidLoad]; UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom ]; [button addTarget:self action:@selector (pressOn) forControlEvents:UIControlEventTouchUpInside ]; [button setTitle:@"加载Flutter" forState:UIControlStateNormal ]; [button setBackgroundColor:[UIColor blueColor]]; button.frame = CGRectMake ((SCREEN_WIDTH-100 )/2 , 100 , 100 , 60 ); [self .view addSubview:button]; } - (void )pressOn { FlutterViewController *flutterViewController =[FlutterViewController new]; [flutterViewController setInitialRoute:@"route" ]; NSString *channelName = @"com.pages.your/native_get" ; _messageChannel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:flutterViewController]; [self nativeToFlutter]; [self presentViewController:flutterViewController animated:false completion:nil ]; } - (void )nativeToFlutter { sleep(5 ); [_messageChannel invokeMethod:@"NativeToFlutter" arguments:@"NativeToFlutter" ]; } @end
2.Flutter主动与Native app交互 交互主要分为以下几步:
1.Native创建MethodChannel。
2.Native添加HandleBlcok。
3.Flutter发送消息。
Dart代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class HomePage extends StatefulWidget { _HomePageState createState() => _HomePageState(); } class _HomePageState extends State <HomePage > { String title = 'Flutter to Native' ; Color backGroundColor = Colors.red; static const MethodChannel methodChannel = const MethodChannel('com.pages.your/native_get' ); _iOSPushToVC() async { title = await methodChannel.invokeMethod('FlutterToNative' ); setState(() { backGroundColor = Colors.green; }); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: backGroundColor, body: Center( child: GestureDetector( behavior: HitTestBehavior.opaque, child: new Text(title), onTap: (){ _iOSPushToVC(); }, ), ), ); } }
Native代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #import "ViewController.h" #import <Flutter/Flutter.h> #define SCREEN_WIDTH ([[UIScreen mainScreen] bounds].size.width) @interface ViewController ()@property (nonatomic , strong ) FlutterMethodChannel *messageChannel;@end @implementation ViewController - (void )viewDidLoad { [super viewDidLoad]; UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom ]; [button addTarget:self action:@selector (pressOn) forControlEvents:UIControlEventTouchUpInside ]; [button setTitle:@"加载Flutter" forState:UIControlStateNormal ]; [button setBackgroundColor:[UIColor blueColor]]; button.frame = CGRectMake ((SCREEN_WIDTH-100 )/2 , 100 , 100 , 60 ); [self .view addSubview:button]; } - (void )pressOn { FlutterViewController *flutterViewController =[FlutterViewController new]; [flutterViewController setInitialRoute:@"route" ]; NSString *channelName = @"com.pages.your/native_get" ; _messageChannel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:flutterViewController]; __weak typeof (self ) weakSelf = self ; [_messageChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) { __strong typeof (self ) strongSelf = weakSelf; if ([call.method isEqualToString:@"FlutterToNative" ]) { if (result) { result(@"NativeBack" ); } } }]; [self presentViewController:flutterViewController animated:false completion:nil ]; } @end