這是以頁面級做爲獨立的模塊加入,而不是頁面的某個元素。html
好比說原生頁面中只有某一個item是Flutter;android
在作混合開發以前,咱們首先須要建立一個Flutter Module。ios
這裏建議Flutter Module的建立目錄和原生工程的目錄同級。假設Native的目錄是這樣的:xxx/Native項目。git
cd xxx/
flutter create -t module flutter_module
能夠看到生成的flutter_module目錄下有這些文件:github
README.md pubspec.lock
flutter_module.iml pubspec.yaml
flutter_module_android.iml test
.android lib .ios
上面的.android和.ios目錄,是隱藏文件, 也是這個flutter_module的宿主工程。由於有宿主工程的存在,這個flutter_module在不添加額外配置的狀況下是能夠獨立運行的:json
官方解決方案:https://github.com/flutter/flutter/wiki/Add-Flutter-to-existing-appsxcode
【說明】:在原生項目中添加Flutter Module,須要配置好CocoaPods到工程中。若是沒有使用CocoaPods的,能夠參考http://www.javashuo.com/article/p-fdprzbzd-ky.html進行配置。服務器
第一步:在Podfile文件中添加依賴:網絡
flutter_application_path = "../flutter_module" eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)
第二步:安裝依賴:app
在項目的根目錄中,執行以下指令:
pod install
第三步:禁用Bitcode:
目前Flutter還不支持Bitcode,因此集成了Flutter的iOS項目須要禁用Bitcode。
第四步:添加Build Phase來構建Dart代碼。
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed
添加完以後,要將這個Run Script拖動到Target Dependencies phase下面,接下來就能夠運行項目了。
第一步:配置Android項目的Flutter Module依賴:打開Android項目的settings.gradle添加以下代碼。這段腳本的做用是讓Flutter做爲一個單獨的模塊打包進來。
//HybridAndroid/settings.gradle setBinding(new Binding([gradle: this])) evaluate(new File( settingsDir.parentFile, 'flutter_module/.android/include_flutter.groovy' ))
setBinding和evaluate容許Flutter模塊包括它本身在內的任何Flutter插件,在settings.gradle中以相似::flutter、package_info、:video_player的方式存在。
第二步:添加:flutter依賴。
//app/build.gradle //... dependencies { //... implementation project(':flutter') }
在build.gradle中配置的時候,有兩個地方要特別注意:
compileOptions { //編譯須要設置成JAVA8 sourceCompatibility 1.8 targetCompatibility 1.8 } defaultConfig { minSdkVersion 16 //Flutter中要求最低SDK版本爲16 //... }
在OC中調用Flutter Module有兩種方式:
下面咱們分別來看一下這兩種方式。
FlutterViewController* flutterViewController = [[FlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil]; [GeneratedPluginRegistrant registerWithRegistry:flutterViewController]; //若是使用了插件 [flutterViewController setInitialRoute:@"myApp"]; [self.navigationController pushViewController:flutterViewController animated:YES];
經過上面的代碼,咱們能夠看到setInitialRoute方法傳遞了參數「myApp」,該參數用於告訴Dart代碼顯示哪一個Flutter視圖。在Flutter Module的main.dart文件中,須要經過window.defaultRouteName來獲取Native指定要顯示的路由名,以肯定要建立哪一個窗口小部件並傳遞給runApp:
void main() => runApp(_widgetForRoute(window.defaultRouteName)); Widget _widgetForRoute(String route) { switch (route) { case 'myApp': return MyApp(); default: return MaterialApp( home: Center( child: Text('沒找到'), ), ); } }
第一步:須要AppDelegate繼承自FlutterAppDelegate
//AppDelegate.h #import <UIKit/UIKit.h> #import <Flutter/Flutter.h> @interface AppDelegate : FlutterAppDelegate @property (strong, nonatomic) FlutterEngine *flutterEngine; @end //AppDelegate.m - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //FlutterEngine初始化 self.flutterEngine = [[FlutterEngine alloc] initWithName:@"io.flutter" project:nil]; [self.flutterEngine runWithEntrypoint:nil]; [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine]; //有插件 //設置RootVC self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; UIViewController *vc = [[ViewController alloc] init]; UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc]; self.window.rootViewController = nav; self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible]; return [super application:application didFinishLaunchingWithOptions:launchOptions]; }
第二步:經過FlutterEngine來初始化FlutterViewController。
FlutterEngine *flutterEngine = [(AppDelegate *)[[UIApplication sharedApplication] delegate] flutterEngine]; FlutterViewController *flutterViewController = [[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil]; [self.navigationController pushViewController:flutterViewController animated:YES];
由於在AppDelegate中,咱們已經提早初始化了FlutterEngine,因此這種方式打開一個Flutter模塊的速度,比第一種方式要快一些。
【注意】:使用FlutterEngine方式,調用 setInitialRoute 方法會無效,在Flutter端拿到的永遠是「I」,這是Flutter SDK的一個BUG,所以若是必須依賴 setInitialRoute 參數,那麼只能使用方式一進行賦值。
在Java中調用Flutter Module有兩種方式:
fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { View flutterView = Flutter.createView( MainActivity.this, getLifecycle(), "myApp" ); FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(600, 800); layout.leftMargin = 100; layout.topMargin = 200; addContentView(flutterView, layout); } });
fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { FragmentTransaction tx = getSupportFragmentManager().beginTransaction(); tx.replace(R.id.someContainer, Flutter.createFragment("myApp")); tx.commit(); } });
上面都使用了字符串「myApp」來告訴Dart代碼,在Flutter視圖中顯示哪一個widget。在Flutter項目中能夠經過 window.defaultRouteName 來獲取Native傳過來的「myApp」字符串,以肯定要建立哪一個widget並傳遞給runApp。
import 'package:flutter/material.dart'; import 'dart:ui'; import 'package:flutter/services.dart'; void main() => runApp(_widgetForRoute(window.defaultRouteName)); Widget _widgetForRoute(String route) { switch (route) { case 'myApp': return new MyApp(); default: return Center( child: Text('Unknown route: $route', textDirection: TextDirection.ltr), ); } } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Flutter 混合開發'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { static const platform = const MethodChannel('gof.flutter.io/battery'); String _batteryLevel = 'Unknown battery level.'; Future<Null> _getBatteryLevel() async { String batteryLevel; try { final int result = await platform.invokeMethod('getBatteryLevel'); batteryLevel = 'Battery level at $result % .'; } on PlatformException catch(e) { batteryLevel = "Failed to get battery level: '${e.message}'."; } setState(() { _batteryLevel = batteryLevel; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ RaisedButton( child: Text('Get Battery Level'), onPressed: _getBatteryLevel, ), Text(_batteryLevel) ], ), ) ); } }
代碼編寫完以後,就能夠運行項目了。
咱們知道,在作純Flutter開發的時候,它帶有熱重啓/從新加載的功能。可是,在混合開發中,是在原生工程中集成了Flutter項目,這時熱重啓/從新加載的功能失效了,那麼咱們怎樣啓用混合開發中的熱重啓/從新加載功能呢?
【注意】:執行「flutter attach」指令,有可能遇到以下報錯:
NoSuchMethodError: NoSuchMethodError: The getter 'port' was called on null. Receiver: null Tried calling: port
【解決方案】:https://github.com/flutter/flutter/issues/32471
flutter channel master
flutter upgrade
若是有多個模擬器或設備,那麼須要使用以下指令來使用熱重啓/從新加載:
flutter attach -d BD1389B9-FF73-4114-96E9-4EE9A572A2AE
[注意]:熱重啓/從新加載,須要原生工程是debug模式。
上一節講了在混合工程中使用熱重啓/從新加載,一樣的,咱們是否可以調試咱們的Dart代碼呢? 答案是確定的,只須要以下兩個步驟:
接下來就能夠像調試普通Flutter項目同樣來調試混合開發模式的Dart代碼了。
[注意]:調試Dart代碼,須要原生工程是debug模式。
發佈iOS應用,首先須要一個99美圓的我的開發者帳號用於將app上傳到App Store,或者是299美圓的企業級帳號用於將App發佈到公司本身的服務器或者第三方服務器上。
接下來,大概就是這幾個步驟:申請AppID -> 在iTunes Connect建立應用 -> 打包程序 -> 將應用提交到App Store。
發佈Android應用,主要有兩大步驟:簽名打包 -> 發佈到各個Store。
那麼如何簽名打包一個Flutter開發的App呢?
第一步:生成Android簽名證書。簽名APK須要一個證書用於爲APP簽名,生成簽名證書能夠在Android Studio中,以可視化的方式生成,也可使用終端命令的方式生成。
第二步:設置gradle變量。
./gradlew assembleRelease
在講解Flutter與Native之間是如何傳遞數據以前,咱們先來了解一下它們的通訊機制,Flutter與Native的通訊是經過Channel來完成的。
消息使用Channel(平臺通道)在Flutter和Native之間傳遞,以下圖所示:
平臺所支持的數據類型以下表所示:
Flutter定義了三種不一樣類型的Channel:
這三種類型的Channel都是全雙工通訊,即A <=> B,Dart能夠主動發送消息到Native端,而且Native接收消息後能夠作出迴應。一樣地,Native端也能夠主動發送消息到Dart端,Dart端接受消息後返回給Native端。
如今咱們來看看上面三種類型的Channel,在iOS端是怎麼實現的。
咱們先看一下iOS端該Channel的構造函數:
+ (instancetype)messageChannelWithName:(NSString*)name binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger codec:(NSObject<FlutterMessageCodec>*)codec;
在建立好BasicMessageChannel以後,若是要讓其接收到來自Dart端主動發出的消息,則須要設置它的setMessageHandler方法爲其設置一個消息處理器:
- (void)setMessageHandler:(FlutterMessageHandler _Nullable)handler;
FlutterMessageHandler的定義以下:
//message:消息內容 //callback:回覆消息的回調函數 typedef void (^FlutterMessageHandler)(id _Nullable message, FlutterReply callback);
若是要給Dart發送消息,能夠調用以下方法:
- (void)sendMessage:(id _Nullable)message; - (void)sendMessage:(id _Nullable)message reply:(FlutterReply _Nullable)callback;
主動發送數據到Dart和接收來自Dart的消息的代碼,能夠參考:
- (void)initMessageChannel{ self.messageChannel = [FlutterBasicMessageChannel messageChannelWithName:@"BasicMessageChannelPlugin" binaryMessenger:self.flutterViewController codec:[FlutterStringCodec sharedInstance]]; __weak typeof(self)weakSelf = self; //設置消息處理器,處理來自Dart主動發出的消息 [self.messageChannel setMessageHandler:^(NSString* message, FlutterReply reply) { reply([NSString stringWithFormat:@"BasicMessageChannel收到:%@",message]); [weakSelf sendShow:message]; }]; } //使用FlutterBasicMessageChannel發送數據 [self.messageChannel sendMessage:self.tfInput.text reply:^(id _Nullable reply) { if (reply != nil) { [self sendShow:reply]; } }];
接下來咱們看一下Dart端的實現:
仍是先看構造函數:
const BasicMessageChannel(this.name, this.codec);
建立好BasicMessageChannel以後,若是要讓其接收來自Native端主動發出的消息,則須要調用它的setMessageHandler方法爲其設置一個消息處理器。
void setMessageHandler(Future<T> handler(T message))
若是要主動給Native發送消息,能夠調用send方法:
Future<T> send(T message)
主動發送數據到Native和接收來自Native的消息的代碼,能夠參考:
static const BasicMessageChannel<String> _basicMessageChannel = const BasicMessageChannel('BasicMessageChannelPlugin', StringCodec()); //使用BasicMessageChannel接收來自Native主動發出的消息,並向Native回覆 _basicMessageChannel.setMessageHandler((String message) => Future<String>(() { setState(() { showMessage = 'BasicMessageChannel:'+message; }); return "收到Native的消息:" + message; })); //使用BasicMessageChannel向Native發送消息,並接收Native的回覆 String response; try { response = await _basicMessageChannel.send(value); } on PlatformException catch (e) { print(e); }
咱們仍是先從iOS端的構造函數看起:
//建立FlutterStandardMethodCodec類型的codec + (instancetype)methodChannelWithName:(NSString*)name binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger; + (instancetype)methodChannelWithName:(NSString*)name binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger codec:(NSObject<FlutterMethodCodec>*)codec;
在建立好MethodChannel以後,須要設置一個消息處理器,以便可以接收來自Dart端主動發出的消息:
- (void)setMethodCallHandler:(FlutterMethodCallHandler _Nullable)handler;
FlutterMethodCallHandler的定義以下:
typedef void (^FlutterMethodCallHandler)(FlutterMethodCall* call, FlutterResult result);
iOS端的具體使用:
- (void)initMethodChannel{ self.methodChannel = [FlutterMethodChannel methodChannelWithName:@"MethodChannelPlugin" binaryMessenger:self.flutterViewController]; __weak typeof(self)weakSelf = self; [self.methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) { if ([@"send" isEqualToString:call.method]) { result([NSString stringWithFormat:@"MethodChannelPlugin收到:%@",call.arguments]);//返回結果給Dart); [weakSelf sendShow:call.arguments]; } }]; }
接下來看一下Dart端的實現:
MethodChannel的構造函數:
const MethodChannel(this.name, [this.codec = const StandardMethodCodec(), this.binaryMessenger = defaultBinaryMessenger ])
建立好MethodChannel以後,若是要主動給Native端發送消息,能夠調用 invokeMethod 方法:
Future<T> invokeMethod<T>(String method, [ dynamic arguments ])
主動發送數據到Native的代碼,能夠參考:
//初始化 static const MethodChannel _methodChannelPlugin = const MethodChannel('MethodChannelPlugin'); //主動發送數據到Native String response; try { response = await _methodChannelPlugin.invokeMethod('send', value); } on PlatformException catch (e) { print(e); }
先看iOS端的構造函數:
//建立一個FlutterStandardMethodCodec類型的EventChannel + (instancetype)eventChannelWithName:(NSString*)name binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger; + (instancetype)eventChannelWithName:(NSString*)name binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger codec:(NSObject<FlutterMethodCodec>*)codec;
在建立好EventChannel以後,若是要讓其接收Dart發來的消息,須要調用以下方法來設置一個消息處理器:
- (void)setStreamHandler:(NSObject<FlutterStreamHandler>* _Nullable)handler;
@protocol FlutterStreamHandler //Native監聽事件時調用 //arguments:傳遞的參數 //events:Native回調Dart時的回調函數,它提供success、error、endOfStream三個回調方法分別對應事件的不一樣狀態 - (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(FlutterEventSink)events; //Flutter取消監聽時調用 - (FlutterError* _Nullable)onCancelWithArguments:(id _Nullable)arguments; @end
iOS的具體使用:
- (void)initEventChannel{ self.eventChannel = [FlutterEventChannel eventChannelWithName:@"EventChannelPlugin" binaryMessenger:self.flutterViewController]; //設置消息處理器,處理來自Dart的消息 [self.eventChannel setStreamHandler:self]; } #pragma mark - <FlutterStreamHandler> //這個onListen是Flutter端開始監聽這個channel時的回調,第二個參數 EventSink是用來傳數據的載體 - (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(FlutterEventSink)eventSink { //arguments flutter給native的參數 //回調給flutter,建議使用實例指向,由於該block可使用屢次 self.eventSink = eventSink; return nil; } //flutter再也不接收 - (FlutterError* _Nullable)onCancelWithArguments:(id _Nullable)arguments { // arguments flutter給native的參數 self.eventSink = nil; return nil; } //使用EventChannel發送數據 if (self.eventSink != nil) { self.eventSink(message); }
接下來看一下dart端的實現:
dart端主要是實現一個事件的監聽,來接聽來自Native端主動發送的數據,代碼以下:
//初始化 static const EventChannel _eventChannelPlugin = EventChannel('EventChannelPlugin'); StreamSubscription _streamSubscription = _eventChannelPlugin .receiveBroadcastStream('123') .listen(_onToDart, onError: _onToDartError); //消息監聽 void _onToDart(message) { setState(() { showMessage = message; }); } //錯誤 void _onToDartError(error) { print(error); }
略。
Flutter容許咱們在初始化Flutter頁面時,向Flutter傳遞一個string類型的 initialRoute 參數,從這個名字能夠看出,它是用做路由名稱的。既然是string類型的,那麼咱們在初始化Flutter時,就能夠很靈活的去應用了,好比說傳一個json格式的字符串,傳遞更多參數給Flutter端。示例以下:
- (FlutterViewController *)flutterViewController { if (nil == _flutterViewController) { _flutterViewController = [[FlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil]; [_flutterViewController setInitialRoute:@"{'name':'myApp','data':{'userId':'00001','userName':'LeeGof'}}"]; } return _flutterViewController; }
而後在Dart端經過以下方式接收參數:
void main() { String initParams = window.defaultRouteName; runApp(_widgetForRoute(initParams)); }
在Flutter中,Native向Dart傳遞消息能夠經過 BasicMessageChannel 或 EventChannel 來實現。
首先咱們看一下 BasicMessageChannel 方式的實現。
- (void)initMessageChannel{ self.messageChannel = [FlutterBasicMessageChannel messageChannelWithName:@"BasicMessageChannelPlugin" binaryMessenger:self.flutterViewController codec:[FlutterStringCodec sharedInstance]]; __weak typeof(self)weakSelf = self; //設置消息處理器,處理來自Dart的消息 [self.messageChannel setMessageHandler:^(NSString* message, FlutterReply reply) { reply([NSString stringWithFormat:@"BasicMessageChannel收到:%@",message]); [weakSelf sendShow:message]; }]; } - (void)btnSend:(id)sender { [self.view endEditing:YES]; NSString *message = self.tfInput.text; if (message && message.length > 0) { if (self.isEventChannel) { //使用EventChannel發送數據 if (self.eventSink != nil) { self.eventSink(message); } } else { //使用FlutterBasicMessageChannel發送數據 [self.messageChannel sendMessage:message reply:^(id _Nullable reply) { if (reply != nil) { [self sendShow:reply]; } }]; } } }
EventChannel 方式和 BasicMessageChannel 方式相似:
- (void)initEventChannel{ self.eventChannel = [FlutterEventChannel eventChannelWithName:@"EventChannelPlugin" binaryMessenger:self.flutterViewController]; //設置消息處理器,處理來自Dart的消息 [self.eventChannel setStreamHandler:self]; } #pragma mark - <FlutterStreamHandler> //這個onListen是Flutter端開始監聽這個channel時的回調,第二個參數 EventSink是用來傳數據的載體 - (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(FlutterEventSink)eventSink { //arguments flutter給native的參數 //回調給flutter,建議使用實例指向,由於該block可使用屢次 self.eventSink = eventSink; return nil; } //flutter再也不接收 - (FlutterError* _Nullable)onCancelWithArguments:(id _Nullable)arguments { // arguments flutter給native的參數 self.eventSink = nil; return nil; }
Dart端經過以下方式接收數據:
static const BasicMessageChannel<String> _basicMessageChannel = const BasicMessageChannel('BasicMessageChannelPlugin', StringCodec()); //使用BasicMessageChannel接受來自Native的消息,並向Native回覆 _basicMessageChannel .setMessageHandler((String message) => Future<String>(() { setState(() { showMessage = 'BasicMessageChannel:' + message; }); return "收到Native的消息:" + message; })); static const EventChannel _eventChannelPlugin = EventChannel('EventChannelPlugin'); void _onToDart(message) { setState(() { showMessage = 'EventChannel:' + message; }); } void _onToDartError(error) { print(error); }
在Flutter中,Dart向Native傳遞消息能夠經過 BasicMessageChannel 或 MethodChannel 來實現。
首先看一下Dart發送的相關代碼:
String response; try { if (_isMethodChannelPlugin) { //使用BasicMessageChannel向Native發送消息,並接受Native的回覆 response = await _methodChannelPlugin.invokeMethod('send', value); } else { response = await _basicMessageChannel.send(value); } } on PlatformException catch (e) { print(e); } setState(() { showMessage = response ?? ""; });
Native端接收:
- (void)initMessageChannel{ self.messageChannel = [FlutterBasicMessageChannel messageChannelWithName:@"BasicMessageChannelPlugin" binaryMessenger:self.flutterViewController codec:[FlutterStringCodec sharedInstance]]; __weak typeof(self)weakSelf = self; //設置消息處理器,處理來自Dart的消息 [self.messageChannel setMessageHandler:^(NSString* message, FlutterReply reply) { reply([NSString stringWithFormat:@"BasicMessageChannel收到:%@",message]); [weakSelf sendShow:message]; }]; } - (void)initMethodChannel{ self.methodChannel = [FlutterMethodChannel methodChannelWithName:@"MethodChannelPlugin" binaryMessenger:self.flutterViewController]; __weak typeof(self)weakSelf = self; [self.methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) { if ([@"send" isEqualToString:call.method]) { result([NSString stringWithFormat:@"MethodChannelPlugin收到:%@",call.arguments]);//返回結果給Dart); [weakSelf sendShow:call.arguments]; } }]; }