Flutter 與原生通訊的三種方式

語言: CN / TW / HK

Flutter 與原生之間的通訊依賴靈活的訊息傳遞方式

  • 應用的Flutter部分通過平臺通道(platform channel)將訊息傳送到其應用程式的所在的宿主(iOS或Android)應用(原生應用)

  • 宿主監聽平臺通道,並接收該訊息。然後它會呼叫該平臺的 API,並將響應傳送回客戶端,即應用程式的 Flutter 部分。

Flutter 與原生存在三種互動方式,分別為:

  • MethodChannel:用於傳遞方法呼叫(method invocation)通常用來呼叫 native 中某個方法

  • BasicMessageChannel:用於傳遞字串和半結構化的資訊,這個用的比較少

  • EventChannel:用於資料流(event streams)的通訊。有監聽功能,比如電量變化之後直接推送資料給flutter端

三種 Channel 之間互相獨立,各有用途,但它們在設計上卻非常相近。每種 Channel 均有三個重要成員變數:

  • name: String型別,代表 Channel 的名字,也是其唯一識別符號

  • messager:BinaryMessenger 型別,代表訊息信使,是訊息的傳送與接收的工具

  • codec: MessageCodec 型別或 MethodCodec 型別,代表訊息的編解碼器

具體使用

  • 首先分別建立 Native 工程和 Flutter Module。我這裡是以 iOS 端和 Flutter 通訊為例,建立完 iOS 工程後,需要通過 CocoaPods 管理 Flutter Module。

截圖2021-11-27 下午3.09.28.png

  • 然後在 iOS 工程裡面建立 Podfile ,然後引入 Flutter Module ,具體程式碼如下:

``` platform :ios,'11.0' inhibit_all_warnings!

flutter module 檔案路徑

flutter_application_path = '../flutter_module' load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

target 'Native_iOS' do

install_all_flutter_pods(flutter_application_path)

end ``注意:` flutter_application_path 這個是 Flutter 工程的路徑,我是原生專案和 Flutter在一個目錄下

  • 最後在終端 pod install 一下,看是否能正常引入 Flutter Module。這樣就可以在iOS工程裡面匯入#import <Flutter/Flutter.h>

截圖2021-11-27 下午3.20.39.png

一、MethodChannel的使用

這裡寫的程式碼實現了以下功能

1.實現了點選原生頁面的按鈕跳轉到 Flutter 頁面,在 Flutter 點選返回按鈕能正常返回原生頁面

2.實現在Flutter頁面點選當前電量,從原生介面傳值到 Flutter 頁面

原生端程式碼

``` @property (nonatomic, strong)FlutterEngine *flutterEngine;

@property (nonatomic, strong)FlutterViewController *flutterVC;

@property (nonatomic, strong)FlutterMethodChannel *methodChannel;

  • (void)viewDidLoad {     [super viewDidLoad];

//隱藏了原生的導航欄     self.navigationController.navigationBarHidden = YES;

UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 80, 80)];     btn.backgroundColor = [UIColor redColor];     [btn addTarget:self action: @selector(onBtnClick) forControlEvents:UIControlEventTouchUpInside];     [self.view addSubview:btn];

self.flutterVC = [[FlutterViewController alloc] initWithEngine:self.flutterEngine nibName:nil bundle:nil]; //建立channel     self.methodChannel = [FlutterMethodChannel methodChannelWithName:@"methodChannel" binaryMessenger:self.flutterVC.binaryMessenger];

}

  • (void)onBtnClick {

//告訴Flutter對應的頁面 //Method--方法名稱,arguments--引數     [self.methodChannel invokeMethod:@"EnterFlutter" arguments:@""];

//push進入Flutter頁面

[self.navigationController pushViewController:self.flutterVC animated:YES];

__weak __typeof(self) weakSelf = self; //監聽Flutter發來的事件     [self.methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) { //響應從Flutter頁面傳送來的方法         if ([call.method isEqualToString:@"exit"]) {             [weakSelf.flutterVC.navigationController popViewControllerAnimated:YES];         } else if ([call.method isEqualToString:@"getBatteryLevel"]) { //傳值回Flutter頁面             [weakSelf.methodChannel invokeMethod:@"BatteryLevel" arguments:@"60%"];         }     }]; }

//建立引擎,真正在專案中,引擎可以定義為一個單例。這樣處理防止在原生裡面存在多引擎,是非常佔有記憶體的 - (FlutterEngine *)flutterEngine {     if (!_flutterEngine) {         FlutterEngine * engine = [[FlutterEngine alloc] initWithName:@"flutterEngin"];         if (engine.run) {             _flutterEngine = engine;         }     }     return _flutterEngine; } ```

Flutter 端程式碼

``` class _MyHomePageState extends State {

String batteryLevel = '0%'; //定義通道 final MethodChannel _methodhannel = const MethodChannel('com.pages.your/native_get');

@override void initState() { super.initState();

 //Flutter端監聽傳送過來的資料
_methodhannel.setMethodCallHandler((call) {
  if (call.method == 'EnterFlutter') {
    print(call.arguments);
  } else if (call.method == 'BatteryLevel') {
    batteryLevel = call.arguments;
  }
  setState(() {});
  return Future(() {});
});

}

@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( children: [ ElevatedButton( onPressed: () { //傳送訊息給原生 _methodhannel.invokeListMethod('exit'); }, child: Text('返回'), ), ElevatedButton( onPressed: () { //傳送訊息給原生 _oneChannel.invokeListMethod('getBatteryLevel'); }, child: Text('當前電量${batteryLevel}'), ), ], ), ), ); } } ```

二、BasicMessageChannel的使用

它是可以雙端通訊的,Flutter 端可以給 iOS 傳送訊息,iOS 也可以給 Flutter 傳送訊息。這段程式碼實現了在 Flutter 中的 TextField 輸入文字,在 iOS 端能及時輸出。

原生端程式碼

需要在上面程式碼的基礎上增加 MessageChannel ,並接收訊息和傳送訊息

``` @property (nonatomic, strong) FlutterBasicMessageChannel *messageChannel;

self.messageChannel = [FlutterBasicMessageChannel messageChannelWithName:@"messgaeChannel" binaryMessenger:self.flutterVC.binaryMessenger];

[self.messageChannel setMessageHandler:^(id _Nullable message, FlutterReply  _Nonnull callback) {

NSLog(@"收到Flutter的:%@",message);     }]; ```

Flutter 端程式碼

//需要建立和iOS端相同名稱的通道 final messageChannel = const BasicMessageChannel("messgaeChannel", StandardMessageCodec());

監聽訊息

messageChannel.setMessageHandler((message) { print('收到來自iOS的$message'); return Future(() {}); });

傳送訊息

messageChannel.send(str);

三、EventChannel的使用

只能是原生髮送訊息給 Flutter 端,例如監聽手機電量變化,網路變化,感測器等。

我這裡在原生端實現了一個定時器,每隔一秒傳送一個訊息給 Flutter 端,模仿這個功能。

原生端程式碼

記得所在的類要實現這個協議 FlutterStreamHandler

``` //定義屬性 //通道 @property (nonatomic, strong) FlutterEventChannel *eventChannel; //事件回撥 @property (nonatomic, copy) FlutterEventSink events; //用於計數 @property (nonatomic, assign) NSInteger count;

//初始化通道 self.eventChannel = [FlutterEventChannel eventChannelWithName:@"eventChannel" binaryMessenger:self.flutterVC.binaryMessenger];

[self.eventChannel setStreamHandler:self];

//呼叫建立定時器 [self createTimer];

//建立定時器 - (void)createTimer {

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector: @selector(timeStart) userInfo:nil repeats:YES]; }

//傳送訊息 - (void)timeStart{

self.count += 1;     NSDictionary *dic = [NSDictionary dictionaryWithObject:@(self.count) forKey:@"count"];     if (self.events != nil) {         self.events(dic);     } }

//代表通道已經建好,原生端可以傳送資料了 - (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(FlutterEventSink)eventSink {

self.events = eventSink;     return nil; }

//代表Flutter端不再接收 - (FlutterError* _Nullable)onCancelWithArguments:(id _Nullable)arguments {

self.events = nil;     return nil; } ```

Flutter 端程式碼

``` //建立通道 final EventChannel eventChannel = const EventChannel('eventChannel');

//開始監聽資料 eventChannel.receiveBroadcastStream().listen((event) { print(event.toString()); }); ```

以上就是iOS原生和Flutter通訊的幾種方法。歡迎關注、點贊及轉發。