本文主要針對現有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<HomePage> {
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<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代碼
#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];
// 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<HomePage> {
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 <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];
// 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 <deviceId>' 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機制