本文主要针对现有iOS项目想接入flutter,怎么接入flutter,如何进行项目管理,以及Native和flutter之间如何调用,如何调试来讲解的。
一、创建Flutter Module
执行下面的命令创建Flutter Moudle
cd some/path/
flutter create --template module my_flutter
some/path/
是你要存放工程的目录,然后创建flutter Module,这一步要注意,不要创建成flutter project项目了,执行命令后,控制台会打印:
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文件结构如下:
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 中添加下面配置
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)
target 'MyApp' do
install_all_flutter_pods(flutter_application_path)
end
这里的MyApp就是对应iOS项目的名称,存放到自己项目对应target中就好了。
1.3 pod install
在Podfile所在目录,执行pod install,如果没问题,会在你的项目中增加以下依赖:
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
提示错误如下:
[!] 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目录下运行如下命令:
open -a Simulator
flutter build ios
查看能否正确运行,如果提示以下错误,则是证书问题:
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)
方法二:
使用如下命令,忽略签名:
flutter build ios --release --no-codesign
配置成功之后,再次运行flutter build ios
,会打印如下信息:
Automatically signing iOS for device deployment using specified development team in Xcode project:
56XB5ELH9A
Running 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
flutter build ios-framework --output=../Flutter/
命令执行成功后,会在my_flutter同一级目录下,产生Flutter的文件,文件结构如下:
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"
Failed to find assets path for "flutter_assets"
[VERBOSE-2:engine.cc(114)] Engine run configuration was invalid.
如果报上面的错误,则在my_flutter中运行以下命令:
flutter clean
flutter build ios
问题2.dyld: Library not loaded: @rpath/Flutter.framework/Flutter
这个问题是说明嵌入frameWork有问题,可以检查一下,Embed Framework和Link Binary With Libraries
。
2.4 方案优缺点
优点:
- 1.团队成员不依赖flutter环境
缺点:
- 1.打包配置,比较麻烦,都需要手动操作。
3.使用Flutter framework和CocoaPods集成(本地)
3.1生成frameWork
在Flutter v1.13.6之后版本,支持--cocoapods参数,可以使用下面命令。
flutter build ios-framework --cocoapods --output=../Flutter/
生成如下文件结构:
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文件
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私有库
$ pod lib create MyFlutterFramework
终端执行代码:
xingkunkun:FlutterForFW admin$ pod lib create MyFlutterFramework
Cloning `https://github.com/CocoaPods/pod-template.git` into `MyFlutterFramework `.
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 Module步骤,
flutter create --template module my_flutter
- 2.构建framework
$ flutter build ios --debug
或者
flutter build ios --release --no-codesign(选择不需要证书)
- 3.检查.ios目录下
- 是否有Flutter-->App.framework
- 是否有Flutter-->engine-->Flutter.framework
.ios目录下
Flutter-->App.framework
Flutter-->engine-->Flutter.framework
4.3将CocoaPods私有库集成到Native项目中
在MyFlutterFramework中创建ios_frameworks文件夹,并将App.framework
和Flutter.framework
拷贝进去。
在MyFlutterFramework的podspec文件中,添加以下配置:
s.static_framework = true
arr = Array.new
arr.push('ios_frameworks/*.framework')
s.ios.vendored_frameworks = arr
之后在MyFlutterFramework的podfile同级目录中执行
$ pod install
在MyApp工程下的podfile文件中添加
platform :ios, '8.0'
target 'MyApp' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
# Pods for MyApp
pod 'MyFlutterFramework', :path => '../MyFlutterFramework'
end
之后在MyApp的podfile同级目录中执行
$ pod install
这时在MyApp中,就可以找到App.framework
和Flutter.framework
4.4将MyFlutterFramework和my_flutter推送到远程仓库
- 1.MyFlutterFramework和my_flutter推送到远程仓库
- 2.修改MyApp工程下的podfile,将
pod 'MyFlutterFramework'
依赖修改为MyFlutterFramework远程连接。
platform :ios, '8.0'
target 'MyApp' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
# Pods for MyApp
pod 'MyFlutterFramework', :git=>'https://gitlab.com/MyFlutterFramework.git'
end
-
- 如果MyFlutterFramework中的ios_frameworks不详推送到远程仓库,可以在gitignore文件中添加一下
# 忽略ios_frameworks中文件
ios_frameworks
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代码
class HomePage extends StatefulWidget {
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State {
String title = 'Flutter to Native';
Color backGroundColor = Colors.red;
// 注册一个通知
static const MethodChannel methodChannel = const MethodChannel('com.pages.your/native_get');
_HomePageState(){
//Native调用Dart方法
methodChannel.setMethodCallHandler((MethodCall call){
if(call.method == "NativeToFlutter"){
setState(() {
title = call.arguments;
backGroundColor = Colors.yellow;
});
}
return Future.value();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: backGroundColor,
body: Center(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
child: new Text(title),
onTap: (){
_iOSPushToVC();
},
),
),
);
}
}
Native代码
#import "ViewController.h"
#import
#define SCREEN_WIDTH ([[UIScreen mainScreen] bounds].size.width)
@interface ViewController ()
@property (nonatomic, strong) FlutterMethodChannel *messageChannel;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
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";// 要与main.dart中一致
_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代码
class HomePage extends StatefulWidget {
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State {
String title = 'Flutter to Native';
Color backGroundColor = Colors.red;
// 注册一个通知
static const MethodChannel methodChannel = const MethodChannel('com.pages.your/native_get');
//Dart调用Native方法,并接收返回值。
_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代码
#import "ViewController.h"
#import
#define SCREEN_WIDTH ([[UIScreen mainScreen] bounds].size.width)
@interface ViewController ()
@property (nonatomic, strong) FlutterMethodChannel *messageChannel;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
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";// 要与main.dart中一致
_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
四、app调试
混合开发的时候,需要在XCode调试代码,在调试的过程中,怎么调试Dart代码呢?或者能不能使用热加载?
1.调试Dart代码
在混合开发过程中,在iOS项目中,我们如何调试dart代码呢?
- 1.关闭我们的app
- 2.点击Android Studio工具栏上的Flutter Attach按钮
- 点击之后会提示Waiting for a connection from Flutter on iPhone 11 Pro...
- 3.启动我们的app
- 启动app之后,会提示Syncing files to device iPhone 11 Pro...
接下来就可以像调试普通Flutter项目一样来调试混合开发模式下的Dart代码了。
2.热加载
- 1.关闭我们的app
- 2.在terminal中运行 flutter attach命令。
$ flutter attach
Waiting for a connection from Flutter on iPhone 11 Pro Max...
注意,这里如果提示有多个设备,如下所示:
More than one device connected; please specify a device with the '-d ' flag, or use '-d all' to act on all devices.
我的 iPhone • 00008030-000445611146802E • ios • iOS 13.3
iPhone 11 Pro Max • 67FCC5B2-DA5D-4EF0-8DE1-53E8F8C4CBA9 • ios • com.apple.CoreSimulator.SimRuntime.iOS-13-2 (simulator)
可以使用以下命令:
flutter attach -d 67FCC5B2-DA5D-4EF0-8DE1-53E8F8C4CBA9 //67FCC5B2-DA5D-4EF0-8DE1-53E8F8C4CBA9是设备对应的id
- 3.启动app,启动之后会有如下提示,就代表成功了。
Syncing files to device iPhone 11 Pro Max...
4,196ms (!)
To hot reload changes while running, press "r". To hot restart (and rebuild state), press "R".
An Observatory debugger and profiler on iPhone 11 Pro Max is available at: http://127.0.0.1:62889/T9DjblAu03w=/
For a more detailed help message, press "h". To detach, press "d"; to quit, press "q".
接下来就可以在terminal调试了:
r : 热加载;
R : 热重启;
h : 获取帮助;
d : 断开连接;
q : 退出;
参考资料:
Integrate a Flutter module into your iOS project
Upgrading Flutter added to existing iOS Xcode project
Add Flutter to existing apps
flutter集成进iOS工程
闲鱼flutter-boot介绍
优雅的 Flutter 组件化 混编方案
Flutter和原生iOS交互
Flutter混合开发(二):iOS项目集成Flutter模块详细指南
深入理解Flutter Platform Channel
Flutter混合开发二-FlutterBoost使用介绍
如何用 Flutter 实现混合开发?
深入理解Flutter的Platform Channel机制