iOS 与 Flutter 通信之 MethodChannel
iOS 与 Flutter 通信之 MethodChannel
1. MethodChannel 简介
MethodChannel
是 iOS 与 Flutter 通信的一种方式, 是一种双向通信, 主要用来互相调用方法, 同时可以传递参数, 也可以接收返回的数据, 是一次性的通信, 一旦通信结束, 想要再次通信, 需要重新调用对应的方法.
本文正好也解决一下 iOS 嵌入 Flutter 这篇文章中, 加载 flutter
页面卡顿的情况. 其实也比较简单, 就是 FlutterEngine
和 FlutterViewController
都用单例来管理, 提前初始化, 在程序中只保存一份, 也不需要销毁. 这样做有两个好处, 一个是解决了第一次加载卡顿问题, 另一个是内存泄漏问题.
MethodChannel 使用类似 与 JS 交互用的 WebViewJavascriptBridge.
2. MethodChannel 使用场景
假设项目是 iOS 原生嵌入 Flutter
这么一个场景, iOS 原生嵌入 Flutter 的配置流程可以看篇文章 iOS 嵌入 Flutter
2.1 iOS 调用 flutter
flutter_module
中根据 iOS 调用不同的 method
, 展示不同的页面. flutter_module
中监听到 iOS 原生调用之后, 加载页面, 然后再给 iOS 端返回一些数据.
2.2 flutter 调用 iOS
点击 flutter_module
页面中的按钮, 调用 iOS 的method
, iOS 端收到调用后 dismiss
掉当前页面, 再通过 block 给 flutter_module
页面回调一引起数据.
注意:
两端各自收到调用之后的回调数据, 可以有, 也可以没有, 都有对应的 API, 根据自己具体需求确定, 因为本文是在讲述用法, 就全写上了.
2.3 场景效果
操作效果
控制台输出
3. 使用方法
3.0 Flutter 引擎初始化
首先要说的是, 这只是测试 Demo, 并没有封装成单例, 但是也把 FlutterEngine 和 FlutterViewController 做为 ViewController 的属性, 在 viewDidLoad
中初始化 FlutterEngine 和FlutterViewController, 并启动 FlutterEngine. 效果与单例类似, 实际开发中, 大家可以根据自己的情况来灵活运用, 理解万岁 ????????????.
完整代码放在最后
@interface ViewController () @property (nonatomic, strong) FlutterEngine *flutterEngine; @property (nonatomic, strong) FlutterViewController *flutterVC; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; FlutterViewController *vc = [[FlutterViewController alloc] initWithEngine:self.flutterEngine nibName:nil bundle:nil]; // 全屏时 flutter 页面就需要写回调来告诉原生 dismiss vc.modalPresentationStyle = UIModalPresentationFullScreen; self.flutterVC = vc; } - (FlutterEngine *)flutterEngine { if (!_flutterEngine) { FlutterEngine *flutterEngine = [[FlutterEngine alloc] initWithName:@"andy"]; // FlutterEngine 需要 run 起来, 有可能失败. // 所以只有 run 成功的时候才赋值. if ([flutterEngine run]) { _flutterEngine = flutterEngine; } } return _flutterEngine; } @end复制代码
3.1 MethodChannel 初始化
3.1.1 iOS 初始化
此处的 FlutterMethodChannel 也可以做为属性记录下来
, 大家可以自己尝试一下.
FlutterMethodChannel *methodChannel = [FlutterMethodChannel methodChannelWithName:@"channel_1" binaryMessenger:self.flutterVC.binaryMessenger];复制代码
3.1.2 flutter 初始化
final MethodChannel _channel_1 = const MethodChannel('channel_1');复制代码
3.2 iOS 调用 flutter
3.2.1 iOS 中的调用
iOS 中对 flutter 的调用, 对应的是 flutter 中的监听. 当一个 method
调用过去的时候, flutter 根据 method name
做出不同的响应, 完成不同的任务, 在完成任务后, 还可以返回给 iOS 端数据.
参数1
:NSString
类型, 是 method name, 用来表示我们要做什么事情参数2
:id
类型, 是传递给 flutter 端的参数,参数3
: FlutterResult 类型, 是一个 block. 用来接收 flutter 传回 iOS 的数据.FlutterResult
: 参数 result 是 id 类型.typedef void (^FlutterResult)(id _Nullable result);复制代码
没有回调的
``` [methodChannel invokeMethod:@"one" arguments:@"来自 ios channel_1 的参数"]; ```复制代码
有回调的
``` // 调用 flutter // Method : Method 名称 // arguments : 传递给 flutter 的数据 // result : 是 flutter 回调回来的数据 [methodChannel invokeMethod:@"one" arguments:@"来自 ios channel_1 的参数" result:^(id _Nullable result) { NSLog(@"ios: %@", result); }]; ```复制代码
3.2.2 flutter 中的监听
handler
: 逆名函数,call:
MethodCall
类型参数call
, 具体在下面单独讲解.返回值: 需要给 iOS 回调数据就给一个返回值, 不需要就不给返回值, 但是要加上
async
, 具体位置看代码.call
: 有两个属性,method
和arguments
method
:String
类型, iOS 调用 flutter 时传递的method name
. 可以判断method
来完成不同的任务.arguments
:dynamic
类型, iOS 调用 flutter 时传递的参数, 根据对应method
的参数约定类型解析即可.
没有给 iOS 回传数据的
_channel_1.setMethodCallHandler((call) async { setState(() { // 此处一个 channel 可以调用很多 method, if (call.method == 'two') { pageIndex = call.method; } }); });复制代码
有给 iOS 回传数据的
// 接收 ios _channel_1 的调用, 并返回给 ios 一个数据回调 _channel_1.setMethodCallHandler((call) { setState(() { // 此处一个 channel 可以调用很多 method, // if (call.method == 'two') { pageIndex = call.method; // } }); return Future.value('_channel_1: flutter 响应 ios 后, 回调给 ios 的数据 111'); });复制代码
3.3 flutter 调用 iOS
3.3.1 flutter 中的调用
此处涉及到 async
所以我把按钮的点击方法参数onPressed
也带过来了,
参数1
:String
类型,method name
, 对应 iOS 端监听中 call 的method
属性, 以此判断需要完成的任务.参数2
: 动态类型, 调用method
同时传递给iOS
中的参数, 对应 iOS 端监听中 call 的arguments
属性.
不接收 iOS 回传数据
onPressed: () { _channel_1.invokeMethod('dismiss', 'one: 来自 flutter 的参数 hello one'); },复制代码
接收 iOS 回传数据
onPressed: () async { var result = await _channel_1.invokeMethod('dismiss', 'one: 来自 flutter 的参数 hello one'); print(result.toString()); },复制代码
3.3.2 iOS 中的监听
Handler: block, 两个参数
call
和result
, 具体下面讲.call
:FlutterMethodCall
类型, 两个属性method
: NSString 类型, method name, 对应 flutter 调用时的method
, 以此来判断需要执行的任务.arguments
:method
对应的参数. id 类型.result
:FlutterResult
类型, 用来给 flutter 返回数据, 是一个 block, 需要返回就调用, 不需要就不调用, 参数是 id 类型.typedef void (^FlutterResult)(id _Nullable result);复制代码
不给 flutter 调用返回数据的
// flutter 调用 ios // call : method 名称和 参数 // method -> string // arguments -> id // result : 给 flutter 回调的数据, 参数是 id 类型 [methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) { if ([call.method isEqual:@"dismiss"]) { NSLog(@"%@", call.arguments); [self dismissViewControllerAnimated:YES completion:nil]; } }];复制代码
给 flutter 调用返回数据的
// flutter 调用 ios // call : method 名称和 参数 // method -> string // arguments -> id // result : 给 flutter 回调的数据, 参数是 id 类型 [methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) { if ([call.method isEqual:@"dismiss"]) { NSLog(@"%@", call.arguments); [self dismissViewControllerAnimated:YES completion:nil]; } result(@"channel_1: ios 响应 flutter 调用 dismiss 方法后返回给 flutter 页面的数据"); }];复制代码
3.4 数据传递建议
二者相互传递数据的类型建议用 json
格式字符串, 这样方便封装数据解析, 在传递数据前, 将模型数据转换成 json
字符串; 在收到数据之后, 解析成模型, 这些操作大家肯定也都熟悉的很, 可以完美对接我们现在的工具链.
4. 总结
MethodChannel 通信特点:
用于二者互相调用, 可带参数, 可接收返回值.
双向通信
一次性通信
iOS
端调用要在主线程
5. 完整代码
5.1 iOS 原生代码
#import "ViewController.h" #import <Flutter/Flutter.h> @interface ViewController () @property (nonatomic, strong) FlutterEngine *flutterEngine; @property (nonatomic, strong) FlutterViewController *flutterVC; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; FlutterViewController *vc = [[FlutterViewController alloc] initWithEngine:self.flutterEngine nibName:nil bundle:nil]; // 全屏时 flutter 页面就需要写回调来告诉原生 dismiss vc.modalPresentationStyle = UIModalPresentationFullScreen; self.flutterVC = vc; } - (FlutterEngine *)flutterEngine { if (!_flutterEngine) { FlutterEngine *flutterEngine = [[FlutterEngine alloc] initWithName:@"andy"]; // FlutterEngine 需要 run 起来, 有可能失败. // 所以只有 run 成功的时候才赋值. if ([flutterEngine run]) { _flutterEngine = flutterEngine; } } return _flutterEngine; } // one page - (IBAction)pushFlutter:(id)sender { // 初始化 FlutterMethodChannel FlutterMethodChannel *methodChannel = [FlutterMethodChannel methodChannelWithName:@"channel_1" binaryMessenger:self.flutterVC.binaryMessenger]; // 调用 flutter // Method : Method 名称 // arguments : 传递给 flutter 的数据 // result : 是 flutter 回调回来的数据 [methodChannel invokeMethod:@"one" arguments:@"来自 ios channel_1 的参数" result:^(id _Nullable result) { NSLog(@"ios: %@", result); }]; [self presentViewController:self.flutterVC animated:YES completion:nil]; // flutter 调用 ios // call : method 名称和 参数 // method -> string // arguments -> id // result : 给 flutter 回调的数据, 参数是 id 类型 [methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) { if ([call.method isEqual:@"dismiss"]) { NSLog(@"%@", call.arguments); [self dismissViewControllerAnimated:YES completion:nil]; } result(@"channel_1: ios 响应 flutter 调用 dismiss 方法后返回给 flutter 页面的数据"); }]; } // two page - (IBAction)pushFlutter_2:(id)sender { // 传递参数 FlutterMethodChannel *methodChannel = [FlutterMethodChannel methodChannelWithName:@"channel_2" binaryMessenger:self.flutterVC.binaryMessenger]; [methodChannel invokeMethod:@"two" arguments:@"来自 ios channel_2 的参数" result:^(id _Nullable result) { NSLog(@"ios: %@", result); }]; [self presentViewController:self.flutterVC animated:YES completion:nil]; // flutter 调用 ios 的响应处理 并通过 result 回调返回给 flutter 一个数据 [methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) { if ([call.method isEqual:@"dismiss"]) { NSLog(@"%@", call.arguments); [self dismissViewControllerAnimated:YES completion:nil]; } result(@"channel_2: ios 响应 flutter 调用 dismiss 方法后返回给 flutter 页面的数据"); }]; } @end复制代码
5.2 Flutter 代码
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; void main() => runApp(const MyApp()); class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { String pageIndex = ''; final MethodChannel _channel_1 = const MethodChannel('channel_1'); final MethodChannel _channel_2 = const MethodChannel('channel_2'); @override void initState() { // TODO: implement initState super.initState(); // 接收 ios _channel_1 的调用, 并返回给 ios 一个数据回调 _channel_1.setMethodCallHandler((call) { setState(() { // 此处一个 channel 可以调用很多 method, if (call.method == 'one') { print(call.arguments); pageIndex = call.method; } }); return Future.value('_channel_1: flutter 响应 ios 后, 回调给 ios 的数据 111'); }); // 接收 ios _channel_2 的调用, 并返回给 ios 一个数据回调 _channel_2.setMethodCallHandler((call) { setState(() { // 此处一个 channel 可以调用很多 method, if (call.method == 'two') { print(call.arguments); pageIndex = call.method; } }); return Future.value('_channel_2: flutter 响应 ios 后, 回调给 ios 的数据 222'); }); } @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Module', theme: ThemeData( primarySwatch: Colors.blue ), home: _rootPage(pageIndex), ); } Widget _rootPage(String pageIndex) { switch(pageIndex) { case 'one': return Scaffold( appBar: AppBar( title: Text(pageIndex), ), body: Center( child: ElevatedButton( child: Text(pageIndex), onPressed: () async { var result = await _channel_1.invokeMethod('dismiss', 'one: 来自 flutter 的参数 hello one'); print(result); }, ), ), ); case 'two': return Scaffold( appBar: AppBar( title: Text(pageIndex), ), body: Center( child: ElevatedButton( child: Text(pageIndex), onPressed: () async { var result = await _channel_2.invokeMethod('dismiss', 'two: 来自 flutter 的参数 hello two'); print(result); }, ), ), ); } return Scaffold( appBar: AppBar( title: const Text('hello flutter'), ), body: const Center( child: Text('hello flutter'), ), ); } }